diff --git a/README.md b/README.md index c274b94c..c52ac3f2 100644 --- a/README.md +++ b/README.md @@ -215,16 +215,69 @@ 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 | |-------|----------| | Bot not responding | Check `TELEGRAM_TOKEN` and `ADMIN_USER_ID` in `.env` | -| Access pending | Admin must approve the user via `/admin` or the admin flow from `/start` | -| Commands failing | Verify Hummingbot Backend API is running and reachable | -| Connection refused | Check host and port under `/servers` for the active server | -| Auth error | Verify API username/password in `/servers` | -| DEX features unavailable | Ensure Gateway is configured and running (`/gateway`) | +| Access pending | Admin must approve user via /config > Admin Panel | +| Commands failing | Verify Hummingbot API is running | +| 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 3ef88a67..f473574c 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, chat_ws, executors, market, portfolio, positions, reports, routines, servers, settings, transcribe, 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/main.py b/main.py index b7ece4cb..51c95f38 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( diff --git a/setup-environment.sh b/setup-environment.sh index 02f48f4d..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) + # Determine web dashboard access 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" - 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 @@ -412,20 +435,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" - 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" + 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_PORT=8088" >> "$ENV_FILE" + echo "WEB_URL=http://$(escape_env_value "$SERVER_IP"):8088" >> "$ENV_FILE" fi fi else @@ -433,8 +449,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 +492,74 @@ 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." + 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" + + # ── Tailscale option for external API ────────────── + use_tailscale_remote="${use_tailscale_early:-N}" + 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..." + 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" + # 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 + + hb_api_configured=true else # Check Docker (only for hummingbot-api launch) if ! command_exists docker; then @@ -490,6 +573,52 @@ else docker_available=true fi + # ── Tailscale option for Docker deploy ───────────── + TS_DEPLOY=false + ts_auth_key="" + ts_hb_hostname="hummingbot-api" + use_tailscale="${use_tailscale_early:-N}" + 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..." + 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 + msg_ok "Tailscale connected — hummingbot-api will be reachable at http://$ts_hb_hostname:8000" + # 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 "" prompt_visible "API admin username" "admin" "hb_username" prompt_secret "API admin password" "admin" "hb_password" @@ -527,13 +656,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 +835,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 +844,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 +908,36 @@ 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" +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" +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 ""