Skip to content
Merged
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
51 changes: 47 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: setup run deploy stop install uninstall build install-pre-commit
.PHONY: setup run deploy stop install uninstall build install-pre-commit tailscale-status

SETUP_SENTINEL := .setup-complete

Expand All @@ -9,13 +9,56 @@ $(SETUP_SENTINEL):
./setup.sh

# Run locally (dev mode)
# When TAILSCALE_ENABLED=true: installs Tailscale if needed, connects, configures tailscale serve,
# then binds uvicorn to 127.0.0.1 only (tailscale serve exposes port 8000 on the tailnet)
run:
docker compose up emqx postgres -d
conda run --no-capture-output -n hummingbot-api uvicorn main:app --reload
@set -a; [ -f .env ] && . ./.env; set +a; \
if [ "$${TAILSCALE_ENABLED:-false}" = "true" ]; then \
echo "[INFO] Tailscale mode: setting up Tailscale for source install..."; \
if ! command -v tailscale >/dev/null 2>&1; then \
echo "[INFO] Installing Tailscale..."; \
curl -fsSL https://tailscale.com/install.sh | sh; \
fi; \
if ! tailscale status >/dev/null 2>&1; then \
echo "[INFO] Connecting to Tailscale network..."; \
sudo tailscale up --authkey="$${TAILSCALE_AUTH_KEY}" --hostname="$${TAILSCALE_HOSTNAME:-hummingbot-api}" --accept-dns=true; \
fi; \
tailscale serve status 2>/dev/null | grep -q ":8000" || \
sudo tailscale serve --bg http:8000 http://localhost:8000; \
echo "[INFO] Binding uvicorn to 127.0.0.1 (tailscale serve exposes port 8000 on tailnet)"; \
conda run --no-capture-output -n hummingbot-api uvicorn main:app --reload --host 127.0.0.1 --port 8000; \
else \
conda run --no-capture-output -n hummingbot-api uvicorn main:app --reload; \
fi

# Deploy with Docker
# When TAILSCALE_ENABLED=true: adds the Tailscale sidecar compose override
deploy: $(SETUP_SENTINEL)
docker compose up -d
@set -a; [ -f .env ] && . ./.env; set +a; \
if [ "$${TAILSCALE_ENABLED:-false}" = "true" ]; then \
echo "[INFO] Deploying with Tailscale sidecar..."; \
docker compose -f docker-compose.yml -f docker-compose.tailscale.yml up -d; \
else \
docker compose up -d; \
fi

TAILSCALE_CONTAINER := hummingbot-tailscale

# Show Tailscale connection status (Docker sidecar or local install)
tailscale-status:
@if docker ps --format '{{.Names}}' 2>/dev/null | grep -qx '$(TAILSCALE_CONTAINER)'; then \
echo "[INFO] Tailscale sidecar (Docker)"; \
docker exec $(TAILSCALE_CONTAINER) tailscale status; \
elif command -v tailscale >/dev/null 2>&1; then \
echo "[INFO] Tailscale (local)"; \
tailscale status; \
else \
echo "Tailscale is not available."; \
echo " Docker deploy: ensure TAILSCALE_ENABLED=true and run 'make deploy'"; \
echo " Source run: use 'make run' with Tailscale enabled (installs locally)"; \
exit 1; \
fi

# Stop all services
stop:
Expand Down Expand Up @@ -45,4 +88,4 @@ install-pre-commit:

# Build Docker image
build:
docker build -t hummingbot/hummingbot-api:latest .
docker build -t hummingbot/hummingbot-api:latest .
80 changes: 78 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,15 @@ The script clones **`hummingbot-api`**, runs **`make setup`** (creates **`.env`*

That's it! The API is now running at http://localhost:8000

- **API:** http://localhost:8000
- **Swagger:** http://localhost:8000/docs
| Command | Description |
|---------|-------------|
| `make setup` | Create `.env` file with configuration |
| `make deploy` | Start all services (API, PostgreSQL, EMQX) |
| `make stop` | Stop all services |
| `make run` | Run API locally in dev mode |
| `make install` | Install conda environment for development |
| `make build` | Build Docker image |
| `make tailscale-status` | Show Tailscale connection status |

## Services

Expand Down Expand Up @@ -89,6 +96,69 @@ GATEWAY_URL=... # Gateway URL (for DEX)

Edit `.env` and restart with `make deploy` to apply changes.

## Secure Connection via Tailscale

[Tailscale](https://tailscale.com) creates a private WireGuard network (tailnet) that makes the API accessible only to devices on your tailnet — no open ports, no firewall rules needed.

Use this when running on a VPS or cloud server and want to access the API privately from another machine (e.g. Condor or MCP tools).

### 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` and answer `y` when prompted:

> Use Tailscale for secure private networking? [y/N]

This adds the following to `.env`:

```bash
TAILSCALE_ENABLED=true
TAILSCALE_AUTH_KEY=tskey-auth-...
TAILSCALE_HOSTNAME=hummingbot-api # MagicDNS hostname on your tailnet
```

### Deploy

```bash
make deploy
```

When `TAILSCALE_ENABLED=true`, this automatically runs:

```bash
docker compose -f docker-compose.yml -f docker-compose.tailscale.yml up -d
```

A Tailscale sidecar container joins your tailnet using `network_mode: host`. The API is then reachable at `http://hummingbot-api:8000` from any device on the same tailnet — port 8000 is not exposed publicly.

### Connecting MCP tools via Tailscale

Once on the same tailnet, use the MagicDNS hostname instead of `localhost`:

```bash
claude mcp add --transport stdio hummingbot -- \
docker run --rm -i \
-e HUMMINGBOT_API_URL=http://hummingbot-api:8000 \
-v hummingbot_mcp:/root/.hummingbot_mcp \
hummingbot/hummingbot-mcp:latest
```

### Dev mode

When `TAILSCALE_ENABLED=true`, `make run` will automatically install Tailscale if needed, connect to your tailnet, and bind uvicorn to `127.0.0.1` only (Tailscale handles external access).

### Check status

```bash
make tailscale-status
```

## API Features

- **Portfolio**: Balances, positions, P&L across all exchanges
Expand Down Expand Up @@ -125,6 +195,12 @@ make deploy # Fresh start
docker ps | grep hummingbot
```

**Tailscale not connecting?**
```bash
make tailscale-status # Check tailnet peers
```
Confirm the node appears in `tailscale status` and that MagicDNS is enabled in your Tailscale admin console.

## Support

- **API Docs**: http://localhost:8000/docs
Expand Down
30 changes: 30 additions & 0 deletions docker-compose.tailscale.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Tailscale sidecar overlay — use with:
# docker compose -f docker-compose.yml -f docker-compose.tailscale.yml up -d
#
# network_mode: host lets the Tailscale daemon create a real tailscale0 interface
# on the host, making hummingbot-api's port 8000 reachable at the Tailscale IP
# without exposing it on any public interface.
#
# The base docker-compose.yml emqx-bridge network is unaffected — emqx and
# postgres still communicate with hummingbot-api normally via that bridge.

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:
108 changes: 89 additions & 19 deletions setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,46 @@ COMPOSE_ALREADY_PRESENT=false

has_cmd() { command -v "$1" >/dev/null 2>&1; }

prompt_tty() {
local message="$1"
local default_value="${2:-}"
local value=""
local fd
if [[ -t 0 ]]; then
read -r -p "$message" value
elif { exec {fd}<>/dev/tty; } 2>/dev/null; then
printf '%s' "$message" >&${fd}
read -r value <&${fd}
exec {fd}>&-
elif IFS= read -r value; then
:
else
value=""
fi
echo "${value:-$default_value}"
}

prompt_yes_no() {
local message="$1"
local default_value="${2:-n}"
local value
value="$(prompt_tty "$message" "$default_value")"
[[ "$value" =~ ^[Yy]$ ]]
}

prompt_required_tty() {
local message="$1"
local value=""
while true; do
value="$(prompt_tty "$message" "")"
if [[ -n "$value" ]]; then
echo "$value"
return 0
fi
echo "[WARN] This value cannot be empty"
done
}

resolve_script_dir() {
local src="${BASH_SOURCE[0]}"
while [ -h "$src" ]; do
Expand Down Expand Up @@ -356,28 +396,45 @@ fi

echo "Hummingbot API Setup"
echo ""
echo "Set API credentials (use a strong username, password, and config password):"
echo ""

# Use /dev/tty for prompts to work correctly when called from parent scripts
if [[ -c /dev/tty ]] && [[ -r /dev/tty ]]; then
read -p "API username [default: admin]: " USERNAME < /dev/tty
else
read -p "API username [default: admin]: " USERNAME
fi
USERNAME=${USERNAME:-admin}
USERNAME="$(prompt_required_tty "API username: ")"
PASSWORD="$(prompt_required_tty "API password: ")"
CONFIG_PASSWORD="$(prompt_required_tty "Config password: ")"

if [[ -c /dev/tty ]] && [[ -r /dev/tty ]]; then
read -p "API password [default: admin]: " PASSWORD < /dev/tty
else
read -p "API password [default: admin]: " PASSWORD
fi
PASSWORD=${PASSWORD:-admin}
# --------------------------
# Tailscale Configuration
# --------------------------
TAILSCALE_ENABLED=false
TAILSCALE_AUTH_KEY=""
TAILSCALE_HOSTNAME="hummingbot-api"

if [[ -c /dev/tty ]] && [[ -r /dev/tty ]]; then
read -p "Config password [default: admin]: " CONFIG_PASSWORD < /dev/tty
else
read -p "Config password [default: admin]: " CONFIG_PASSWORD
if prompt_yes_no "Use Tailscale for secure private networking? [y/N]: " "n"; then
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " How to get a Tailscale auth key:"
echo " 1. Create a free account at https://tailscale.com"
echo " 2. Go to: https://tailscale.com/admin/settings/keys"
echo " 3. Click 'Generate auth key'"
echo " 4. Check 'Reusable' for multiple server deployments"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
while true; do
TAILSCALE_AUTH_KEY="$(prompt_tty "Tailscale auth key (tskey-auth-...): " "")"
if [[ -z "$TAILSCALE_AUTH_KEY" ]]; then
echo "[WARN] Auth key cannot be empty"
continue
fi
if [[ ! "$TAILSCALE_AUTH_KEY" =~ ^tskey-auth- ]]; then
echo "[WARN] Auth key must start with 'tskey-auth-'"
continue
fi
break
done
# Hostname defaults to "hummingbot-api" — override via TAILSCALE_HOSTNAME in .env if needed
TAILSCALE_ENABLED=true
fi
CONFIG_PASSWORD=${CONFIG_PASSWORD:-admin}

cat > .env << EOF
# Hummingbot API Configuration
Expand All @@ -401,6 +458,11 @@ GATEWAY_PASSPHRASE=admin

# Paths
BOTS_PATH=$(pwd)

# Tailscale
TAILSCALE_ENABLED=$TAILSCALE_ENABLED
TAILSCALE_AUTH_KEY=$TAILSCALE_AUTH_KEY
TAILSCALE_HOSTNAME=$TAILSCALE_HOSTNAME
EOF

touch .setup-complete
Expand All @@ -415,5 +477,13 @@ echo " make deploy"
echo ""
echo "Option 2: Run API locally (dev mode)"
echo " make install # Creates the conda environment - Note: Please install the latest Anaconda version manually"
echo " make run # Run API"
echo " make run # Run API (installs and connects Tailscale automatically if TAILSCALE_ENABLED=true)"
if [ "$TAILSCALE_ENABLED" = true ]; then
echo ""
echo "Tailscale:"
echo " Docker deploy: Tailscale sidecar starts automatically with 'make deploy'"
echo " Source run: Tailscale installs and connects automatically with 'make run'"
echo " Condor URL: http://$TAILSCALE_HOSTNAME:8000"
echo " Status: make tailscale-status"
fi
echo ""
Loading