From 4b86ff5a87b80e5215458451b3c38d9fbbe8bdb8 Mon Sep 17 00:00:00 2001 From: david-hummingbot Date: Fri, 8 May 2026 00:35:00 +0800 Subject: [PATCH 1/4] feat: add Tailscale support for secure private networking --- Makefile | 2 +- README.md | 61 ++++++++++- condor/web/app.py | 27 +++-- setup-environment.sh | 245 ++++++++++++++++++++++++++++++++++++++----- 4 files changed, 299 insertions(+), 36 deletions(-) diff --git a/Makefile b/Makefile index 20cc30d1..328379bc 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ help: @echo "" @echo " make setup - Interactive setup wizard" @echo " make install - Setup + install all dependencies" - @echo " make run - Run locally (dev)" +\t@echo " make run - Run locally (dev)" @echo " make test - Run tests" @echo " make lint - Run black + isort" diff --git a/README.md b/README.md index 64512677..875446c6 100644 --- a/README.md +++ b/README.md @@ -13,22 +13,20 @@ A Telegram bot for monitoring and trading with Hummingbot via the Backend API. ## Quick Start -**Prerequisites:** Python 3.12+, [uv](https://docs.astral.sh/uv/), Hummingbot Backend API running, Telegram Bot Token +**Prerequisites:** Python 3.12+, [uv](https://docs.astral.sh/uv/), Telegram Bot Token ```bash git clone https://github.com/hummingbot/condor.git cd condor -# Condor (source only) make install # Interactive setup + uv deps + AI CLI tools make run # Start the bot ``` -If you need to run Hummingbot API locally, it can still be launched with Docker from the sibling repo: +To start Hummingbot API separately: ```bash -cd ../hummingbot-api -docker compose up -d +cd ../hummingbot-api && docker compose up -d ``` ## Commands @@ -185,6 +183,57 @@ chat_defaults: {} audit_log: [] ``` +## Secure Connection via Tailscale + +[Tailscale](https://tailscale.com) creates a private WireGuard network (tailnet) so Condor can reach Hummingbot API securely without exposing port 8000 publicly. + +Use this when: +- Hummingbot API is running on a remote server or VPS +- You want an encrypted private connection without opening firewall ports + +### Prerequisites: Get a Tailscale auth key + +1. Create a free account at [tailscale.com](https://tailscale.com) +2. Go to **Settings → Keys**: [tailscale.com/admin/settings/keys](https://tailscale.com/admin/settings/keys) +3. Click **Generate auth key** — check **Reusable** for multiple deployments +4. Copy the key (starts with `tskey-auth-`) + +### Setup + +Run `make setup` — the wizard supports two Tailscale scenarios: + +**Scenario A — Deploy Hummingbot API locally with Tailscale** + +Choose `Y` to deploy Hummingbot API locally, then `y` when asked about Tailscale. The wizard will: +- Clone and start `hummingbot-api` with a Tailscale sidecar container +- Install Tailscale on this machine and connect with hostname `condor` +- Update `config.yml` to reach the API at `http://hummingbot-api:8000` + +**Scenario B — Connect to a remote Hummingbot API via Tailscale** + +Choose `N` to skip local deployment, enter the remote API URL, then `y` when asked about Tailscale. The wizard will: +- Install Tailscale on this machine and connect with hostname `condor` +- Update `config.yml` to use the Tailscale MagicDNS hostname of the remote API + +The remote machine must be running [hummingbot-api-tailscale](https://github.com/hummingbot/hummingbot-api) on the same tailnet. + +### Network layout + +``` +[Condor] tailnet: condor ──WireGuard──► [API] tailnet: hummingbot-api + http://hummingbot-api:8000 +``` + +Both machines must be on the same Tailscale account. + +### Verify the connection + +```bash +tailscale status +``` + +Both `condor` and `hummingbot-api` should appear as connected peers. + ## Troubleshooting | Issue | Solution | @@ -195,6 +244,8 @@ audit_log: [] | Connection refused | Check server host:port in `/config` | | Auth error | Verify server credentials | | DEX features unavailable | Ensure Gateway is configured and running | +| Tailscale: can't reach API | Run `tailscale status` — confirm both peers are connected | +| Tailscale: auth key rejected | Key must start with `tskey-auth-`, check expiry in Tailscale admin | ## Development diff --git a/condor/web/app.py b/condor/web/app.py index b260e458..b68c9f25 100644 --- a/condor/web/app.py +++ b/condor/web/app.py @@ -1,6 +1,8 @@ from __future__ import annotations +import os from pathlib import Path +from urllib.parse import urlparse from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware @@ -10,17 +12,30 @@ from condor.web.routes import agents, archived, auth, backtesting, bots, executors, market, portfolio, positions, reports, routines, servers, ws +def _build_cors_origins() -> list[str]: + """Build CORS allowed origins from env, including WEB_URL for Tailscale/VPS deployments.""" + web_url = os.environ.get("WEB_URL", "").strip().rstrip("/") + web_port = int(os.environ.get("WEB_PORT", "8088") or "8088") + origins = [ + "http://localhost:5173", + "http://127.0.0.1:5173", + f"http://localhost:{web_port}", + ] + if web_url: + parsed = urlparse(web_url) + origin = f"{parsed.scheme}://{parsed.netloc}" + if origin not in origins: + origins.append(origin) + return origins + + def create_app() -> FastAPI: app = FastAPI(title="Condor Dashboard API", version="0.1.0") - # CORS – allow Vite dev server and common local origins + # CORS – allow Vite dev server, local origins, and WEB_URL origin (e.g. Tailscale hostname) app.add_middleware( CORSMiddleware, - allow_origins=[ - "http://localhost:5173", - "http://127.0.0.1:5173", - "http://localhost:8088", - ], + allow_origins=_build_cors_origins(), allow_credentials=True, allow_methods=["*"], allow_headers=["*"], diff --git a/setup-environment.sh b/setup-environment.sh index 02f48f4d..cb27bb47 100755 --- a/setup-environment.sh +++ b/setup-environment.sh @@ -389,11 +389,11 @@ else done ADMIN_USER_ID="$admin_id" - # Prompt for Server IP (optional - for VPS deployments) + # Prompt for Server IP (optional - for VPS deployments without Tailscale) echo "" - msg_info "If running on a VPS, enter the server's IP address." - msg_info "Otherwise, press Enter to use localhost." - prompt_visible "Server IP address (or press Enter for localhost)" "" "server_ip" + msg_info "If running on a VPS without Tailscale, enter the server's public IP." + msg_info "Skip this (press Enter) if you're using Tailscale — it will set the web URL automatically." + prompt_visible "Server IP address (or press Enter for localhost/Tailscale)" "" "server_ip" SERVER_IP="$server_ip" # Write .env (preserve extra vars if file exists) @@ -412,20 +412,13 @@ else echo "ADMIN_USER_ID=$(escape_env_value "$ADMIN_USER_ID")" >> "$ENV_FILE" fi - # Add WEB_URL and WEB_PORT if server IP was provided + # Add WEB_URL if server IP was provided if [ -n "$SERVER_IP" ]; then if grep -q "^WEB_URL=" "$ENV_FILE"; then - sed -i.bak "s|^WEB_URL=.*|WEB_URL=http://$(escape_env_value "$SERVER_IP")|" "$ENV_FILE" + sed -i.bak "s|^WEB_URL=.*|WEB_URL=http://$(escape_env_value "$SERVER_IP"):8088|" "$ENV_FILE" rm -f "$ENV_FILE.bak" else - echo "WEB_URL=http://$(escape_env_value "$SERVER_IP")" >> "$ENV_FILE" - fi - - if grep -q "^WEB_PORT=" "$ENV_FILE"; then - sed -i.bak "s|^WEB_PORT=.*|WEB_PORT=8088|" "$ENV_FILE" - rm -f "$ENV_FILE.bak" - else - echo "WEB_PORT=8088" >> "$ENV_FILE" + echo "WEB_URL=http://$(escape_env_value "$SERVER_IP"):8088" >> "$ENV_FILE" fi fi else @@ -433,8 +426,7 @@ else echo "TELEGRAM_TOKEN=$(escape_env_value "$TELEGRAM_TOKEN")" echo "ADMIN_USER_ID=$(escape_env_value "$ADMIN_USER_ID")" if [ -n "$SERVER_IP" ]; then - echo "WEB_URL=http://$(escape_env_value "$SERVER_IP")" - echo "WEB_PORT=8088" + echo "WEB_URL=http://$(escape_env_value "$SERVER_IP"):8088" fi } > "$ENV_FILE" fi @@ -477,6 +469,65 @@ else if [[ "${deploy_hb:-}" =~ ^[Nn]$ ]]; then echo "DEPLOY_HUMMINGBOT_API=false" >> "$ENV_FILE" msg_ok "Skipped Hummingbot API deployment" + echo "" + msg_info "Enter the Hummingbot API connection details." + prompt_visible "API URL + port (e.g. http://your-server:8000)" "http://localhost:8000" "hb_api_url_raw" + hb_api_url_raw="${hb_api_url_raw:-http://localhost:8000}" + prompt_visible "API admin username" "admin" "hb_username" + prompt_secret "API admin password" "admin" "hb_password" + + HB_API_PROTOCOL=$(python3 -c "from urllib.parse import urlparse; p=urlparse('${hb_api_url_raw}'); print(p.scheme or 'http')" 2>/dev/null || echo "http") + HB_API_HOST=$(python3 -c "from urllib.parse import urlparse; p=urlparse('${hb_api_url_raw}'); print(p.hostname or 'localhost')" 2>/dev/null || echo "localhost") + _def_port=$([ "$HB_API_PROTOCOL" = "https" ] && echo "443" || echo "8000") + HB_API_PORT=$(python3 -c "from urllib.parse import urlparse; p=urlparse('${hb_api_url_raw}'); print(p.port or ${_def_port})" 2>/dev/null || echo "$_def_port") + + # ── Tailscale option for external API ────────────── + echo "" + prompt_visible "Is this hummingbot-api accessible via Tailscale? [y/N]" "N" "use_tailscale_remote" + if [[ "${use_tailscale_remote:-}" =~ ^[Yy]$ ]]; then + echo "" + echo -e " ${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}" + echo -e " ${CYAN} How to get a Tailscale auth key:${RESET}" + echo -e " ${CYAN} 1. Create a free account at https://tailscale.com${RESET}" + echo -e " ${CYAN} 2. Go to: https://tailscale.com/admin/settings/keys${RESET}" + echo -e " ${CYAN} 3. Click 'Generate auth key'${RESET}" + echo -e " ${CYAN} 4. Check 'Reusable' for multiple deployments${RESET}" + echo -e " ${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}" + echo "" + while true; do + prompt_visible "Tailscale auth key (tskey-auth-...)" "" "ts_auth_key" + if [ -z "${ts_auth_key:-}" ]; then + msg_warn "Auth key cannot be empty" + continue + fi + if [[ ! "$ts_auth_key" =~ ^tskey-auth- ]]; then + msg_warn "Auth key must start with 'tskey-auth-'" + continue + fi + break + done + # Hostname defaults to "hummingbot-api" — matches the TS_HOSTNAME set on the server + ts_hostname="hummingbot-api" + + msg_info "Installing Tailscale on this machine..." + curl -fsSL https://tailscale.com/install.sh | sh + msg_info "Connecting to Tailscale network..." + sudo tailscale up --authkey="$ts_auth_key" --hostname="condor" --accept-dns=true + + # Use the Tailscale MagicDNS hostname to reach hummingbot-api (plain HTTP — WireGuard encrypts in transit) + HB_API_HOST="$ts_hostname" + HB_API_PORT="8000" + HB_API_PROTOCOL="http" + msg_ok "Tailscale connected — server URL: http://$ts_hostname:8000" + if grep -q "^WEB_URL=" "$ENV_FILE"; then + sed -i.bak "s|^WEB_URL=.*|WEB_URL=http://condor:8088|" "$ENV_FILE" && rm -f "$ENV_FILE.bak" + else + echo "WEB_URL=http://condor:8088" >> "$ENV_FILE" + fi + msg_ok "Web dashboard will be reachable at http://condor:8088" + fi + + hb_api_configured=true else # Check Docker (only for hummingbot-api launch) if ! command_exists docker; then @@ -490,6 +541,49 @@ else docker_available=true fi + # ── Tailscale option for Docker deploy ───────────── + TS_DEPLOY=false + ts_auth_key="" + ts_hb_hostname="hummingbot-api" + echo "" + prompt_visible "Use Tailscale for secure private access? [y/N]" "N" "use_tailscale" + if [[ "${use_tailscale:-}" =~ ^[Yy]$ ]]; then + echo "" + echo -e " ${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}" + echo -e " ${CYAN} How to get a Tailscale auth key:${RESET}" + echo -e " ${CYAN} 1. Create a free account at https://tailscale.com${RESET}" + echo -e " ${CYAN} 2. Go to: https://tailscale.com/admin/settings/keys${RESET}" + echo -e " ${CYAN} 3. Click 'Generate auth key'${RESET}" + echo -e " ${CYAN} 4. Check 'Reusable' for multiple deployments${RESET}" + echo -e " ${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}" + echo "" + while true; do + prompt_visible "Tailscale auth key (tskey-auth-...)" "" "ts_auth_key" + if [ -z "${ts_auth_key:-}" ]; then + msg_warn "Auth key cannot be empty" + continue + fi + if [[ ! "$ts_auth_key" =~ ^tskey-auth- ]]; then + msg_warn "Auth key must start with 'tskey-auth-'" + continue + fi + break + done + # Hostname defaults to "hummingbot-api" — override TAILSCALE_HOSTNAME in hummingbot-api/.env if needed + msg_info "Installing Tailscale on this machine..." + curl -fsSL https://tailscale.com/install.sh | sh + msg_info "Connecting to Tailscale network..." + sudo tailscale up --authkey="$ts_auth_key" --hostname="condor" --accept-dns=true + TS_DEPLOY=true + if grep -q "^WEB_URL=" "$ENV_FILE"; then + sed -i.bak "s|^WEB_URL=.*|WEB_URL=http://condor:8088|" "$ENV_FILE" && rm -f "$ENV_FILE.bak" + else + echo "WEB_URL=http://condor:8088" >> "$ENV_FILE" + fi + msg_ok "Tailscale connected — hummingbot-api will be reachable at http://$ts_hb_hostname:8000" + msg_ok "Web dashboard will be reachable at http://condor:8088" + fi + echo "" prompt_visible "API admin username" "admin" "hb_username" prompt_secret "API admin password" "admin" "hb_password" @@ -527,13 +621,87 @@ DATABASE_URL=postgresql+asyncpg://hbot:hummingbot-api@localhost:5432/hummingbot_ GATEWAY_URL=http://localhost:15888 GATEWAY_PASSPHRASE=${hb_config_password} BOTS_PATH=${hb_api_abs_path} +TAILSCALE_ENABLED=${TS_DEPLOY} +TAILSCALE_AUTH_KEY=${ts_auth_key} +TAILSCALE_HOSTNAME=${ts_hb_hostname} HBEOF msg_ok "Hummingbot API .env configured" - # Launch hummingbot-api via Docker if available + # Generate docker-compose.tailscale.yml if Tailscale is enabled + if [ "$TS_DEPLOY" = true ] && [ ! -f "$HB_API_DIR/docker-compose.tailscale.yml" ]; then + cat > "$HB_API_DIR/docker-compose.tailscale.yml" << 'TSEOF' +services: + tailscale: + image: tailscale/tailscale:latest + container_name: hummingbot-tailscale + network_mode: host + environment: + - TS_AUTHKEY=${TAILSCALE_AUTH_KEY} + - TS_STATE_DIR=/var/lib/tailscale + - TS_USERSPACE=false + - TS_HOSTNAME=${TAILSCALE_HOSTNAME:-hummingbot-api} + volumes: + - tailscale_state:/var/lib/tailscale + - /dev/net/tun:/dev/net/tun + cap_add: + - NET_ADMIN + - NET_RAW + restart: unless-stopped +volumes: + tailscale_state: +TSEOF + msg_ok "docker-compose.tailscale.yml created" + fi + + # Patch hummingbot-api Makefile so future 'make deploy' stays Tailscale-aware + if [ "$TS_DEPLOY" = true ] && [ -f "$HB_API_DIR/Makefile" ]; then + python3 - "$HB_API_DIR/Makefile" << 'PYEOF' +import sys +with open(sys.argv[1]) as f: + content = f.read() +old = "# Deploy with Docker\ndeploy: $(SETUP_SENTINEL)\n\tdocker compose up -d" +new = ( + "# Deploy with Docker (Tailscale-aware: reads TAILSCALE_ENABLED from .env)\n" + "deploy: $(SETUP_SENTINEL)\n" + "\t@set -a; [ -f .env ] && . ./.env; set +a; \\\n" + "\tif [ \"$${TAILSCALE_ENABLED:-false}\" = \"true\" ]; then \\\n" + "\t\techo \"[INFO] Deploying with Tailscale sidecar...\"; \\\n" + "\t\tdocker compose -f docker-compose.yml -f docker-compose.tailscale.yml up -d; \\\n" + "\telse \\\n" + "\t\tdocker compose up -d; \\\n" + "\tfi" +) +content = content.replace(old, new) +content = content.replace( + ".PHONY: setup run run-https deploy stop install uninstall build install-pre-commit generate-certs show-certs", + ".PHONY: setup run run-https deploy stop install uninstall build install-pre-commit generate-certs show-certs tailscale-status" +) +if "tailscale-status" not in content: + content += ( + "\n# Show Tailscale connection status\n" + "tailscale-status:\n" + "\t@if command -v tailscale >/dev/null 2>&1; then \\\n" + "\t\ttailscale status; \\\n" + "\telse \\\n" + "\t\techo \"Tailscale is not installed or not on PATH.\"; \\\n" + "\tfi\n" + ) +with open(sys.argv[1], "w") as f: + f.write(content) +PYEOF + msg_ok "Hummingbot API Makefile patched for Tailscale-aware deploy" + fi + + # Deploy if Docker is available if [ "$docker_available" = true ] && [ -f "$HB_API_DIR/docker-compose.yml" ]; then msg_info "Starting Hummingbot API stack..." - if (cd "$HB_API_DIR" && docker compose up -d 2>/dev/null); then + if [ "$TS_DEPLOY" = true ] && [ -f "$HB_API_DIR/docker-compose.tailscale.yml" ]; then + _compose_cmd="docker compose -f docker-compose.yml -f docker-compose.tailscale.yml up -d" + msg_info "Using Tailscale sidecar overlay..." + else + _compose_cmd="docker compose up -d" + fi + if (cd "$HB_API_DIR" && eval "$_compose_cmd" 2>/dev/null); then msg_ok "Hummingbot API stack started" # Wait for API to be healthy @@ -632,7 +800,7 @@ if grep -q "DATE_PLACEHOLDER" "$CONFIG_FILE" 2>/dev/null; then config_updated=true fi -# If API was deployed, sync credentials to config.yml +# If API was deployed, sync credentials (and Tailscale host if applicable) to config.yml if [ "${hb_api_deployed:-}" = true ]; then # Determine credentials (re-read from HB API .env if we didn't just set them) if [ -z "${hb_username:-}" ] && [ -f "$HB_API_DIR/.env" ]; then @@ -641,19 +809,35 @@ if [ "${hb_api_deployed:-}" = true ]; then fi if [ -n "${hb_username:-}" ]; then - # Update config.yml 'local' server credentials using sed if grep -A5 "servers:" "$CONFIG_FILE" | grep -q "username:"; then sed -i.bak "/servers:/,/^[^ ]/ s/username: .*/username: $hb_username/" "$CONFIG_FILE" rm -f "$CONFIG_FILE.bak" fi - if grep -A5 "servers:" "$CONFIG_FILE" | grep -q "password:"; then sed -i.bak "/servers:/,/^[^ ]/ s/password: .*/password: $hb_password/" "$CONFIG_FILE" rm -f "$CONFIG_FILE.bak" fi - msg_ok "Synced API credentials to $CONFIG_FILE" fi + + # When Tailscale is enabled, update the server host to the Tailscale MagicDNS hostname + # so condor reaches hummingbot-api via the encrypted tailnet even on the same machine + if [ "${TS_DEPLOY:-false}" = true ]; then + sed -i.bak "/servers:/,/^[^ ]/ s/host: .*/host: $ts_hb_hostname/" "$CONFIG_FILE" && rm -f "$CONFIG_FILE.bak" + sed -i.bak "/servers:/,/^[^ ]/ s/port: .*/port: 8000/" "$CONFIG_FILE" && rm -f "$CONFIG_FILE.bak" + msg_ok "Updated server host to Tailscale hostname: $ts_hb_hostname" + fi +fi + +# If user provided a remote API URL (skipped local deployment), update config.yml +if [ "${hb_api_configured:-false}" = true ] && [ -f "$CONFIG_FILE" ]; then + sed -i.bak "/servers:/,/^[^ ]/ s/host: .*/host: $HB_API_HOST/" "$CONFIG_FILE" && rm -f "$CONFIG_FILE.bak" + sed -i.bak "/servers:/,/^[^ ]/ s/port: .*/port: $HB_API_PORT/" "$CONFIG_FILE" && rm -f "$CONFIG_FILE.bak" + if [ -n "${hb_username:-}" ]; then + sed -i.bak "/servers:/,/^[^ ]/ s/username: .*/username: $hb_username/" "$CONFIG_FILE" && rm -f "$CONFIG_FILE.bak" + sed -i.bak "/servers:/,/^[^ ]/ s/password: .*/password: $hb_password/" "$CONFIG_FILE" && rm -f "$CONFIG_FILE.bak" + fi + msg_ok "Configured $CONFIG_FILE: ${HB_API_PROTOCOL:-http}://${HB_API_HOST}:${HB_API_PORT}" fi if [ "$config_updated" = false ] && [ -f "$CONFIG_FILE" ]; then @@ -689,8 +873,21 @@ echo -e " ${BOLD}Next steps:${RESET}" echo -e " ${BOLD}make install${RESET} Install Python dependencies" echo -e " ${BOLD}make run${RESET} Run Condor locally (dev)" if [ "${hb_api_deployed:-}" = true ]; then -echo -e " ${BOLD}Hummingbot API${RESET} Credentials configured in ../hummingbot-api/.env" -echo -e " ${BOLD}Start API${RESET} cd ../hummingbot-api && docker compose up -d" +echo "" +echo -e " Hummingbot API is running — config at ${BOLD}../hummingbot-api/.env${RESET}" +fi +if [ "${TS_DEPLOY:-false}" = true ]; then +echo "" +echo -e " ${BOLD}Tailscale:${RESET}" +echo -e " hummingbot-api URL: http://${ts_hb_hostname}:8000" +echo -e " Web dashboard URL: http://condor:8088" +echo -e " Tailscale status: tailscale status" +elif [[ "${use_tailscale_remote:-}" =~ ^[Yy]$ ]]; then +echo "" +echo -e " ${BOLD}Tailscale:${RESET}" +echo -e " hummingbot-api URL: http://${ts_hostname:-hummingbot-api}:8000" +echo -e " Web dashboard URL: http://condor:8088" +echo -e " Tailscale status: tailscale status" fi echo -e "${BOLD}══════════════════════════════════════════════${RESET}" echo "" From ff227af19be2736dffdd6fd7fdbb3512cf811acc Mon Sep 17 00:00:00 2001 From: david-hummingbot <85695272+david-hummingbot@users.noreply.github.com> Date: Sat, 9 May 2026 05:50:20 +0800 Subject: [PATCH 2/4] Fix formatting of 'make run' command in Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 328379bc..20cc30d1 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ help: @echo "" @echo " make setup - Interactive setup wizard" @echo " make install - Setup + install all dependencies" -\t@echo " make run - Run locally (dev)" + @echo " make run - Run locally (dev)" @echo " make test - Run tests" @echo " make lint - Run black + isort" From 4ee6cc79cef3134135ad13062f93411a7740e7f1 Mon Sep 17 00:00:00 2001 From: david-hummingbot <85695272+david-hummingbot@users.noreply.github.com> Date: Sun, 10 May 2026 01:41:35 +0800 Subject: [PATCH 3/4] Enhance localhost detection logic Refactor localhost check to include hostname parsing. --- main.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/main.py b/main.py index 382ef56e..e83aeae6 100644 --- a/main.py +++ b/main.py @@ -4,6 +4,7 @@ import os import sys from pathlib import Path +from urllib.parse import urlparse from telegram import BotCommand, InlineKeyboardButton, InlineKeyboardMarkup, Update from telegram.error import NetworkError @@ -53,7 +54,8 @@ async def web_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> Non token = create_login_token(user.id, user.username or "", user.first_name or "") url = f"{WEB_URL}/login?token={token}" - is_localhost = "localhost" in WEB_URL or "127.0.0.1" in WEB_URL + _hostname = urlparse(WEB_URL).hostname or "" + is_localhost = "localhost" in WEB_URL or "127.0.0.1" in WEB_URL or "." not in _hostname if is_localhost: await update.message.reply_text( From b923a68eecef92dc9a48a6c96039dffc102d52f3 Mon Sep 17 00:00:00 2001 From: david-hummingbot <85695272+david-hummingbot@users.noreply.github.com> Date: Sun, 10 May 2026 01:41:59 +0800 Subject: [PATCH 4/4] Implement Tailscale daemon startup and configuration Add Tailscale support for WSL2 and update web dashboard URL handling. --- setup-environment.sh | 112 +++++++++++++++++++++++++++++++------------ 1 file changed, 81 insertions(+), 31 deletions(-) diff --git a/setup-environment.sh b/setup-environment.sh index cb27bb47..c2e92964 100755 --- a/setup-environment.sh +++ b/setup-environment.sh @@ -105,6 +105,22 @@ command_exists() { command -v "$1" >/dev/null 2>&1 } +# On WSL2, systemd doesn't manage tailscaled — we must start the daemon manually +# before calling `tailscale up`, otherwise the call silently fails. +tailscale_up() { + if grep -qi microsoft /proc/version 2>/dev/null; then + if ! pgrep -x tailscaled >/dev/null 2>&1; then + msg_info "Starting Tailscale daemon (WSL2)..." + sudo mkdir -p /var/run/tailscale /var/lib/tailscale + sudo tailscaled --state=/var/lib/tailscale/tailscaled.state \ + --socket=/var/run/tailscale/tailscaled.sock \ + >/dev/null 2>&1 & + sleep 2 + fi + fi + sudo tailscale up "$@" +} + # ── Banner ─────────────────────────────────────────── echo "" @@ -389,12 +405,19 @@ else done ADMIN_USER_ID="$admin_id" - # Prompt for Server IP (optional - for VPS deployments without Tailscale) + # Determine web dashboard access echo "" - msg_info "If running on a VPS without Tailscale, enter the server's public IP." - msg_info "Skip this (press Enter) if you're using Tailscale — it will set the web URL automatically." - prompt_visible "Server IP address (or press Enter for localhost/Tailscale)" "" "server_ip" - SERVER_IP="$server_ip" + prompt_visible "Will you use Tailscale to secure the connection to hummingbot-api? [y/N]" "N" "use_tailscale_early" + SERVER_IP="" + if ! [[ "${use_tailscale_early:-}" =~ ^[Yy]$ ]]; then + echo "" + echo -e " The /web Telegram command sends you a login link for the web dashboard." + echo -e " So we need to know where Condor is running:" + echo -e " ${BOLD}Local machine${RESET} (Mac / Linux / WSL2) — press Enter" + echo -e " ${BOLD}Remote VPS${RESET} — enter the server's public IP" + prompt_visible "Public IP or hostname (press Enter for localhost)" "" "server_ip" + SERVER_IP="${server_ip:-}" + fi # Write .env (preserve extra vars if file exists) if [ -f "$ENV_FILE" ]; then @@ -471,19 +494,24 @@ else msg_ok "Skipped Hummingbot API deployment" echo "" msg_info "Enter the Hummingbot API connection details." - prompt_visible "API URL + port (e.g. http://your-server:8000)" "http://localhost:8000" "hb_api_url_raw" - hb_api_url_raw="${hb_api_url_raw:-http://localhost:8000}" + if [[ "${use_tailscale_early:-}" =~ ^[Yy]$ ]]; then + # Tailscale: host is resolved via MagicDNS after joining the tailnet — no URL needed + HB_API_PROTOCOL="http" + HB_API_HOST="hummingbot-api" + HB_API_PORT="8000" + else + prompt_visible "API URL + port (e.g. http://your-server:8000)" "http://localhost:8000" "hb_api_url_raw" + hb_api_url_raw="${hb_api_url_raw:-http://localhost:8000}" + HB_API_PROTOCOL=$(python3 -c "from urllib.parse import urlparse; p=urlparse('${hb_api_url_raw}'); print(p.scheme or 'http')" 2>/dev/null || echo "http") + HB_API_HOST=$(python3 -c "from urllib.parse import urlparse; p=urlparse('${hb_api_url_raw}'); print(p.hostname or 'localhost')" 2>/dev/null || echo "localhost") + _def_port=$([ "$HB_API_PROTOCOL" = "https" ] && echo "443" || echo "8000") + HB_API_PORT=$(python3 -c "from urllib.parse import urlparse; p=urlparse('${hb_api_url_raw}'); print(p.port or ${_def_port})" 2>/dev/null || echo "$_def_port") + fi prompt_visible "API admin username" "admin" "hb_username" prompt_secret "API admin password" "admin" "hb_password" - HB_API_PROTOCOL=$(python3 -c "from urllib.parse import urlparse; p=urlparse('${hb_api_url_raw}'); print(p.scheme or 'http')" 2>/dev/null || echo "http") - HB_API_HOST=$(python3 -c "from urllib.parse import urlparse; p=urlparse('${hb_api_url_raw}'); print(p.hostname or 'localhost')" 2>/dev/null || echo "localhost") - _def_port=$([ "$HB_API_PROTOCOL" = "https" ] && echo "443" || echo "8000") - HB_API_PORT=$(python3 -c "from urllib.parse import urlparse; p=urlparse('${hb_api_url_raw}'); print(p.port or ${_def_port})" 2>/dev/null || echo "$_def_port") - # ── Tailscale option for external API ────────────── - echo "" - prompt_visible "Is this hummingbot-api accessible via Tailscale? [y/N]" "N" "use_tailscale_remote" + use_tailscale_remote="${use_tailscale_early:-N}" if [[ "${use_tailscale_remote:-}" =~ ^[Yy]$ ]]; then echo "" echo -e " ${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}" @@ -512,19 +540,23 @@ else msg_info "Installing Tailscale on this machine..." curl -fsSL https://tailscale.com/install.sh | sh msg_info "Connecting to Tailscale network..." - sudo tailscale up --authkey="$ts_auth_key" --hostname="condor" --accept-dns=true + tailscale_up --authkey="$ts_auth_key" --hostname="condor" --accept-dns=true + ts_condor_ip=$(tailscale ip -4 2>/dev/null | head -1) # Use the Tailscale MagicDNS hostname to reach hummingbot-api (plain HTTP — WireGuard encrypts in transit) HB_API_HOST="$ts_hostname" HB_API_PORT="8000" HB_API_PROTOCOL="http" msg_ok "Tailscale connected — server URL: http://$ts_hostname:8000" - if grep -q "^WEB_URL=" "$ENV_FILE"; then - sed -i.bak "s|^WEB_URL=.*|WEB_URL=http://condor:8088|" "$ENV_FILE" && rm -f "$ENV_FILE.bak" - else - echo "WEB_URL=http://condor:8088" >> "$ENV_FILE" + # On a VPS (SERVER_IP set), point the web dashboard at the Tailscale IP so the /web link works remotely + if [ -n "${SERVER_IP:-}" ] && [ -n "${ts_condor_ip:-}" ]; then + if grep -q "^WEB_URL=" "$ENV_FILE"; then + sed -i.bak "s|^WEB_URL=.*|WEB_URL=http://$ts_condor_ip:8088|" "$ENV_FILE" && rm -f "$ENV_FILE.bak" + else + echo "WEB_URL=http://$ts_condor_ip:8088" >> "$ENV_FILE" + fi + msg_ok "Web dashboard: http://$ts_condor_ip:8088 (Tailscale access)" fi - msg_ok "Web dashboard will be reachable at http://condor:8088" fi hb_api_configured=true @@ -545,8 +577,7 @@ else TS_DEPLOY=false ts_auth_key="" ts_hb_hostname="hummingbot-api" - echo "" - prompt_visible "Use Tailscale for secure private access? [y/N]" "N" "use_tailscale" + use_tailscale="${use_tailscale_early:-N}" if [[ "${use_tailscale:-}" =~ ^[Yy]$ ]]; then echo "" echo -e " ${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}" @@ -573,15 +604,19 @@ else msg_info "Installing Tailscale on this machine..." curl -fsSL https://tailscale.com/install.sh | sh msg_info "Connecting to Tailscale network..." - sudo tailscale up --authkey="$ts_auth_key" --hostname="condor" --accept-dns=true + tailscale_up --authkey="$ts_auth_key" --hostname="condor" --accept-dns=true + ts_condor_ip=$(tailscale ip -4 2>/dev/null | head -1) TS_DEPLOY=true - if grep -q "^WEB_URL=" "$ENV_FILE"; then - sed -i.bak "s|^WEB_URL=.*|WEB_URL=http://condor:8088|" "$ENV_FILE" && rm -f "$ENV_FILE.bak" - else - echo "WEB_URL=http://condor:8088" >> "$ENV_FILE" - fi msg_ok "Tailscale connected — hummingbot-api will be reachable at http://$ts_hb_hostname:8000" - msg_ok "Web dashboard will be reachable at http://condor:8088" + # On a VPS (SERVER_IP set), point the web dashboard at the Tailscale IP so the /web link works remotely + if [ -n "${SERVER_IP:-}" ] && [ -n "${ts_condor_ip:-}" ]; then + if grep -q "^WEB_URL=" "$ENV_FILE"; then + sed -i.bak "s|^WEB_URL=.*|WEB_URL=http://$ts_condor_ip:8088|" "$ENV_FILE" && rm -f "$ENV_FILE.bak" + else + echo "WEB_URL=http://$ts_condor_ip:8088" >> "$ENV_FILE" + fi + msg_ok "Web dashboard: http://$ts_condor_ip:8088 (Tailscale access)" + fi fi echo "" @@ -880,14 +915,29 @@ if [ "${TS_DEPLOY:-false}" = true ]; then echo "" echo -e " ${BOLD}Tailscale:${RESET}" echo -e " hummingbot-api URL: http://${ts_hb_hostname}:8000" -echo -e " Web dashboard URL: http://condor:8088" +if [ -n "${ts_condor_ip:-}" ] && [ -n "${SERVER_IP:-}" ]; then +echo -e " Web dashboard URL: http://${ts_condor_ip}:8088 ${CYAN}(Tailscale only)${RESET}" +else +echo -e " Web dashboard URL: http://localhost:8088" +fi echo -e " Tailscale status: tailscale status" elif [[ "${use_tailscale_remote:-}" =~ ^[Yy]$ ]]; then echo "" echo -e " ${BOLD}Tailscale:${RESET}" echo -e " hummingbot-api URL: http://${ts_hostname:-hummingbot-api}:8000" -echo -e " Web dashboard URL: http://condor:8088" +if [ -n "${ts_condor_ip:-}" ] && [ -n "${SERVER_IP:-}" ]; then +echo -e " Web dashboard URL: http://${ts_condor_ip}:8088 ${CYAN}(Tailscale only)${RESET}" +else +echo -e " Web dashboard URL: http://localhost:8088" +fi echo -e " Tailscale status: tailscale status" fi +if [ "${TS_DEPLOY:-false}" = true ] || [[ "${use_tailscale_remote:-}" =~ ^[Yy]$ ]]; then +echo "" +echo -e " ${BOLD}Accessing the web dashboard from another device:${RESET}" +echo -e " ${CYAN} Install Tailscale on that device first, then connect with the same key:${RESET}" +echo -e " Linux / WSL: curl -fsSL https://tailscale.com/install.sh | sh && sudo tailscale up --authkey=${ts_auth_key}" +echo -e " macOS / Win: https://tailscale.com/download — then run: sudo tailscale up --authkey=${ts_auth_key}" +fi echo -e "${BOLD}══════════════════════════════════════════════${RESET}" echo ""