Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 58 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
27 changes: 21 additions & 6 deletions condor/web/app.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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=["*"],
Expand Down
4 changes: 3 additions & 1 deletion main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down
Loading