The deploy job hard-failed without CRYPTO_API_TOKEN, and /api/ws (which skips Basic auth) relied on that token. Drop the requirement: the app now works with no token at all, matching the backend guard that was always a no-op when unset. When a token is configured it still protects /api and /api/ws; when it is not, /api/ws is as open as the rest of the API, which the Traefik Basic auth still guards for everything else. Adds tests for the tokenless API and WebSocket.
124 lines
5.6 KiB
YAML
124 lines
5.6 KiB
YAML
name: crypto_trading
|
|
|
|
services:
|
|
backend:
|
|
build: ./backend
|
|
image: crypto_trading-backend
|
|
container_name: crypto-trading-backend
|
|
restart: unless-stopped
|
|
mem_limit: ${CRYPTO_MEM_LIMIT:-1g}
|
|
cpus: ${CRYPTO_CPUS:-2}
|
|
logging:
|
|
driver: json-file
|
|
options:
|
|
max-size: "10m"
|
|
max-file: "5"
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
# Reachable only on the internal network; the frontend nginx proxies to it.
|
|
expose:
|
|
- "8000"
|
|
environment:
|
|
- MODE=${CRYPTO_MODE:-paper}
|
|
- EXCHANGE=${CRYPTO_EXCHANGE:-binance}
|
|
- SYMBOL=${CRYPTO_SYMBOL:-BTC/USDT}
|
|
- TIMEFRAME=${CRYPTO_TIMEFRAME:-5m}
|
|
- POLL_INTERVAL=${CRYPTO_POLL_INTERVAL:-5}
|
|
- STARTING_BALANCE=${CRYPTO_STARTING_BALANCE:-10000}
|
|
- FEE_BPS=${CRYPTO_FEE_BPS:-10}
|
|
- SLIPPAGE_BPS=${CRYPTO_SLIPPAGE_BPS:-5}
|
|
- DATABASE_URL=sqlite:////data/trader.db
|
|
- CORS_ORIGINS=${CRYPTO_CORS_ORIGINS:-*}
|
|
- EXCHANGE_API_KEY=${CRYPTO_EXCHANGE_API_KEY:-}
|
|
- EXCHANGE_API_SECRET=${CRYPTO_EXCHANGE_API_SECRET:-}
|
|
- EXCHANGE_SANDBOX=${CRYPTO_EXCHANGE_SANDBOX:-false}
|
|
- MAX_DAILY_LOSS=${CRYPTO_MAX_DAILY_LOSS:-0}
|
|
- MAX_DRAWDOWN_PCT=${CRYPTO_MAX_DRAWDOWN_PCT:-0}
|
|
- MAX_POSITION_PCT=${CRYPTO_MAX_POSITION_PCT:-1.0}
|
|
- MAX_TRADES_PER_DAY=${CRYPTO_MAX_TRADES_PER_DAY:-0}
|
|
- TELEGRAM_BOT_TOKEN=${CRYPTO_TELEGRAM_BOT_TOKEN:-}
|
|
- TELEGRAM_CHAT_ID=${CRYPTO_TELEGRAM_CHAT_ID:-}
|
|
- NOTIFY_WEBHOOK_URL=${CRYPTO_NOTIFY_WEBHOOK_URL:-}
|
|
- API_TOKEN=${CRYPTO_API_TOKEN:-}
|
|
- API_TOKEN_HEADER=${CRYPTO_API_TOKEN_HEADER:-X-API-Token}
|
|
volumes:
|
|
# Persist the SQLite database across releases. Prefer an absolute host
|
|
# path (CRYPTO_DATA_DIR / PERSISTENT_MOUNT); the repo-relative default
|
|
# can be cleaned by runner maintenance.
|
|
- ${CRYPTO_DATA_DIR:-./data}:/data
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "python -c \"import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:8000/api/health', timeout=5).status==200 else 1)\""]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 20s
|
|
networks:
|
|
- internal
|
|
|
|
frontend:
|
|
build: ./frontend
|
|
image: crypto_trading-frontend
|
|
container_name: crypto-trading-frontend
|
|
restart: unless-stopped
|
|
mem_limit: ${CRYPTO_MEM_LIMIT:-1g}
|
|
cpus: ${CRYPTO_CPUS:-2}
|
|
logging:
|
|
driver: json-file
|
|
options:
|
|
max-size: "10m"
|
|
max-file: "5"
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
depends_on:
|
|
backend:
|
|
condition: service_healthy
|
|
environment:
|
|
- API_TOKEN=${CRYPTO_API_TOKEN:-}
|
|
- API_TOKEN_HEADER=${CRYPTO_API_TOKEN_HEADER:-X-API-Token}
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "wget -q --spider http://127.0.0.1:80/ || exit 1"]
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 10s
|
|
networks:
|
|
- internal
|
|
- docker_default
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.docker.network=docker_default"
|
|
- "traefik.http.routers.crypto-trading.rule=Host(`crypto.${BASE_DOMAIN:-matitos.org}`)"
|
|
- "traefik.http.routers.crypto-trading.entrypoints=websecure"
|
|
- "traefik.http.routers.crypto-trading.tls.certresolver=myresolvercd"
|
|
- "traefik.http.services.crypto-trading.loadbalancer.server.port=80"
|
|
# Basic auth on the WebUI (htpasswd), required: the stack refuses to
|
|
# start when CRYPTO_FRONTEND_AUTH_USERS is unset (no admin/admin
|
|
# fallback). A per-source rate limit reduces credential brute-forcing.
|
|
- "traefik.http.routers.crypto-trading.middlewares=crypto-trading-auth@docker,crypto-trading-sec@docker,crypto-trading-authlimit@docker"
|
|
- "traefik.http.middlewares.crypto-trading-auth.basicauth.users=${CRYPTO_FRONTEND_AUTH_USERS:?CRYPTO_FRONTEND_AUTH_USERS is required (htpasswd string, e.g. htpasswd -nbB user pass)}"
|
|
# Browsers do not reliably replay Basic-auth credentials on a WebSocket
|
|
# upgrade, which left the live feed stuck "reconnecting". Give /api/ws its
|
|
# own higher-priority router that skips Basic auth so the feed connects.
|
|
# If CRYPTO_API_TOKEN is set, the backend still requires it (nginx injects
|
|
# it server-side); if it is unset, /api/ws is as open as the rest of /api.
|
|
- "traefik.http.routers.crypto-trading-ws.rule=Host(`crypto.${BASE_DOMAIN:-matitos.org}`) && PathPrefix(`/api/ws`)"
|
|
- "traefik.http.routers.crypto-trading-ws.entrypoints=websecure"
|
|
- "traefik.http.routers.crypto-trading-ws.tls.certresolver=myresolvercd"
|
|
- "traefik.http.routers.crypto-trading-ws.priority=100"
|
|
- "traefik.http.routers.crypto-trading-ws.middlewares=crypto-trading-sec@docker,crypto-trading-authlimit@docker"
|
|
- "traefik.http.routers.crypto-trading-ws.service=crypto-trading"
|
|
- "traefik.http.middlewares.crypto-trading-authlimit.ratelimit.average=${CRYPTO_AUTH_RATE_LIMIT_AVERAGE:-10}"
|
|
- "traefik.http.middlewares.crypto-trading-authlimit.ratelimit.burst=${CRYPTO_AUTH_RATE_LIMIT_BURST:-5}"
|
|
- "traefik.http.middlewares.crypto-trading-sec.headers.stsSeconds=31536000"
|
|
- "traefik.http.middlewares.crypto-trading-sec.headers.stsIncludeSubdomains=true"
|
|
- "traefik.http.middlewares.crypto-trading-sec.headers.forceSTSHeader=true"
|
|
- "traefik.http.middlewares.crypto-trading-sec.headers.contentTypeNosniff=true"
|
|
- "traefik.http.middlewares.crypto-trading-sec.headers.browserXssFilter=true"
|
|
- "traefik.http.middlewares.crypto-trading-sec.headers.frameDeny=true"
|
|
- "traefik.http.middlewares.crypto-trading-sec.headers.referrerPolicy=strict-origin-when-cross-origin"
|
|
|
|
networks:
|
|
internal:
|
|
docker_default:
|
|
external: true
|