Files
Celes Renata 82892b7a3e
ci/woodpecker/push/test Pipeline was successful
ci/woodpecker/push/build-1 Pipeline was successful
ci/woodpecker/push/build-2 Pipeline was successful
ci/woodpecker/push/build-3 Pipeline was successful
ci/woodpecker/push/finalize Pipeline was successful
Build and Push / lint-and-test (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.adapters.broker_adapter name:broker-adapter]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.aggregation.worker name:aggregation]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.extractor.worker name:extractor]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.ingestion.worker name:ingestion]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.lake_publisher.worker name:lake-publisher]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.parser.worker name:parser]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.recommendation.worker name:recommendation]) (push) Has been cancelled
Build and Push / build-services (map[cmd:python -m services.scheduler.app name:scheduler]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.api.app:app --host 0.0.0.0 --port 8000 name:query-api]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.risk.app:app --host 0.0.0.0 --port 8000 name:risk]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.symbol_registry.app:app --host 0.0.0.0 --port 8000 name:symbol-registry]) (push) Has been cancelled
Build and Push / build-services (map[cmd:uvicorn services.trading.app:app --host 0.0.0.0 --port 8000 name:trading-engine]) (push) Has been cancelled
Build and Push / build-dashboard (push) Has been cancelled
Build and Push / build-superset (push) Has been cancelled
Build and Push / integration-test (push) Has been cancelled
Build and Push / beta-gate (push) Has been cancelled
feat: multi-distro support in deploy-docker.sh
Step 0 now detects the OS and package manager, supporting:
- Debian/Ubuntu (apt)
- RHEL/Rocky/Fedora/CentOS (dnf/yum)
- Arch Linux (pacman)
- openSUSE (zypper)
- WSL (uses host Windows NVIDIA driver, skips driver install)

Handles Docker CE install, NVIDIA driver, NVIDIA Container Toolkit,
and firewall (firewalld + ufw) across all supported distros.
2026-04-29 18:59:40 +00:00

519 lines
18 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
# deploy-docker.sh — Deploy Stonks Oracle to a Docker host via SSH
#
# Usage: bash deploy-docker.sh [OPTIONS]
#
# Options:
# --host USER@HOST SSH target (default: celes@192.168.42.254)
# --ollama-url URL Ollama API URL (default: auto-detect or install)
# --ollama-model MODEL Ollama model name (default: qwen3.5:9b-fast)
# --dir PATH Remote install directory (default: ~/stonks-oracle)
#
# Examples:
# bash deploy-docker.sh
# bash deploy-docker.sh --ollama-url http://10.1.1.12:2701 --ollama-model qwen3.6
# bash deploy-docker.sh --host user@myserver --dir /opt/stonks
# -------------------------------------------------------
# Configuration (override via flags or environment)
# -------------------------------------------------------
REMOTE_HOST="${DEPLOY_HOST:-celes@192.168.42.254}"
REMOTE_DIR="${DEPLOY_DIR:-/home/celes/stonks-oracle}"
OLLAMA_URL="${DEPLOY_OLLAMA_URL:-}"
OLLAMA_MODEL="${DEPLOY_OLLAMA_MODEL:-qwen3.5:9b-fast}"
REPO_URL="http://admin:St0nks0racl3!@10.1.1.12:30300/admin/stonks-oracle.git"
# Parse command-line flags
while [[ $# -gt 0 ]]; do
case $1 in
--host) REMOTE_HOST="$2"; shift 2 ;;
--ollama-url) OLLAMA_URL="$2"; shift 2 ;;
--ollama-model) OLLAMA_MODEL="$2"; shift 2 ;;
--dir) REMOTE_DIR="$2"; shift 2 ;;
*) echo "Unknown option: $1"; exit 1 ;;
esac
done
echo "=== Stonks Oracle Docker Deployment ==="
echo " Target: ${REMOTE_HOST}:${REMOTE_DIR}"
echo " Model: ${OLLAMA_MODEL}"
echo " Ollama: Docker container (GPU-accelerated)"
echo ""
# -------------------------------------------------------
# Step 0: Ensure prerequisites (multi-distro support)
# -------------------------------------------------------
echo "--- Step 0: Checking prerequisites ---"
ssh "$REMOTE_HOST" bash -s <<'REMOTE_SCRIPT'
set -euo pipefail
# --- Detect OS and package manager ---
detect_os() {
if [ -f /etc/os-release ]; then
. /etc/os-release
OS_ID="${ID:-unknown}"
OS_LIKE="${ID_LIKE:-$OS_ID}"
elif [ -f /etc/redhat-release ]; then
OS_ID="rhel"
OS_LIKE="rhel"
else
OS_ID="unknown"
OS_LIKE="unknown"
fi
# Detect WSL
IS_WSL=false
if grep -qi microsoft /proc/version 2>/dev/null; then
IS_WSL=true
fi
# Determine package manager
if command -v apt-get &>/dev/null; then
PKG_MGR="apt"
elif command -v dnf &>/dev/null; then
PKG_MGR="dnf"
elif command -v yum &>/dev/null; then
PKG_MGR="yum"
elif command -v pacman &>/dev/null; then
PKG_MGR="pacman"
elif command -v zypper &>/dev/null; then
PKG_MGR="zypper"
else
PKG_MGR="unknown"
fi
echo " Detected: OS=$OS_ID (like=$OS_LIKE), pkg=$PKG_MGR, WSL=$IS_WSL"
}
install_pkg() {
local pkg="$1"
case "$PKG_MGR" in
apt) sudo apt-get install -y "$pkg" ;;
dnf) sudo dnf -y install "$pkg" ;;
yum) sudo yum -y install "$pkg" ;;
pacman) sudo pacman -S --noconfirm "$pkg" ;;
zypper) sudo zypper install -y "$pkg" ;;
*) echo " ERROR: Unknown package manager"; exit 1 ;;
esac
}
update_pkg_cache() {
case "$PKG_MGR" in
apt) sudo apt-get update -qq ;;
dnf|yum) ;; # dnf/yum auto-refresh
pacman) sudo pacman -Sy ;;
zypper) sudo zypper refresh -q ;;
esac
}
detect_os
# --- Git ---
if ! command -v git &>/dev/null; then
echo " Installing git..."
update_pkg_cache
install_pkg git
echo " ✓ Git installed"
else
echo " ✓ Git present"
fi
# --- Docker Engine ---
if command -v docker &>/dev/null && docker info &>/dev/null; then
echo " ✓ Docker already installed ($(docker --version | cut -d' ' -f3 | tr -d ','))"
else
echo " Installing Docker CE..."
case "$PKG_MGR" in
apt)
# Debian/Ubuntu/WSL
sudo apt-get update -qq
sudo apt-get install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/${OS_ID}/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg 2>/dev/null
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/${OS_ID} $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update -qq
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
;;
dnf|yum)
# RHEL/Rocky/Fedora/CentOS
sudo "$PKG_MGR" -y install dnf-plugins-core 2>/dev/null || true
local repo_distro="rhel"
if [[ "$OS_ID" == "fedora" ]]; then repo_distro="fedora"; fi
sudo dnf config-manager --add-repo "https://download.docker.com/linux/${repo_distro}/docker-ce.repo" 2>/dev/null || \
sudo yum-config-manager --add-repo "https://download.docker.com/linux/${repo_distro}/docker-ce.repo" 2>/dev/null
sudo "$PKG_MGR" -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
;;
pacman)
# Arch Linux
sudo pacman -S --noconfirm docker docker-compose docker-buildx
;;
zypper)
# openSUSE
sudo zypper install -y docker docker-compose docker-buildx
;;
esac
sudo systemctl enable --now docker 2>/dev/null || true
sudo usermod -aG docker "$(whoami)" 2>/dev/null || true
echo " ✓ Docker installed and started"
fi
# --- Docker Compose plugin ---
if docker compose version &>/dev/null; then
echo " ✓ Docker Compose plugin available ($(docker compose version --short))"
else
echo " ERROR: docker compose plugin not found after Docker install"
exit 1
fi
# --- NVIDIA Driver (skip on WSL — uses host driver) ---
if [ "$IS_WSL" = "true" ]; then
echo " ✓ WSL detected — using host Windows NVIDIA driver"
elif ! command -v nvidia-smi &>/dev/null; then
echo " Installing NVIDIA drivers..."
case "$PKG_MGR" in
apt)
sudo apt-get install -y nvidia-driver-560 2>/dev/null || \
sudo apt-get install -y nvidia-driver 2>/dev/null || \
echo " ⚠ NVIDIA driver install failed — install manually"
;;
dnf|yum)
sudo dnf -y install epel-release 2>/dev/null || true
sudo dnf config-manager --add-repo https://developer.download.nvidia.com/compute/cuda/repos/rhel9/x86_64/cuda-rhel9.repo 2>/dev/null || true
sudo dnf -y module install nvidia-driver:latest-dkms 2>/dev/null || \
echo " ⚠ NVIDIA driver install failed — install manually"
;;
pacman)
sudo pacman -S --noconfirm nvidia nvidia-utils 2>/dev/null || \
echo " ⚠ NVIDIA driver install failed — install manually"
;;
zypper)
echo " ⚠ NVIDIA driver: install manually for openSUSE"
;;
esac
else
echo " ✓ NVIDIA driver present ($(nvidia-smi --query-gpu=driver_version --format=csv,noheader | head -1))"
fi
# --- NVIDIA Container Toolkit ---
if command -v nvidia-ctk &>/dev/null; then
echo " ✓ NVIDIA Container Toolkit already installed"
elif [ "$IS_WSL" = "true" ] && docker run --rm --gpus all nvidia/cuda:12.8.0-base-ubuntu24.04 nvidia-smi &>/dev/null 2>&1; then
echo " ✓ WSL GPU passthrough working (no nvidia-ctk needed)"
else
echo " Installing NVIDIA Container Toolkit..."
case "$PKG_MGR" in
apt)
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg 2>/dev/null
curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \
sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list > /dev/null
sudo apt-get update -qq
sudo apt-get install -y nvidia-container-toolkit
;;
dnf|yum)
curl -fsSL https://nvidia.github.io/libnvidia-container/stable/rpm/nvidia-container-toolkit.repo | \
sudo tee /etc/yum.repos.d/nvidia-container-toolkit.repo > /dev/null
sudo "$PKG_MGR" -y install nvidia-container-toolkit
;;
pacman)
sudo pacman -S --noconfirm nvidia-container-toolkit 2>/dev/null || \
echo " ⚠ Install nvidia-container-toolkit from AUR"
;;
zypper)
echo " ⚠ NVIDIA Container Toolkit: install manually for openSUSE"
;;
esac
sudo nvidia-ctk runtime configure --runtime=docker 2>/dev/null || true
sudo systemctl restart docker 2>/dev/null || true
echo " ✓ NVIDIA Container Toolkit installed and Docker configured"
fi
# --- Verify GPU is accessible from Docker ---
if docker run --rm --gpus all nvidia/cuda:12.8.0-base-ubuntu24.04 nvidia-smi &>/dev/null 2>&1; then
echo " ✓ GPU passthrough verified"
else
echo " ⚠ GPU passthrough test failed — may need a reboot or manual NVIDIA setup"
fi
# --- Firewall (open required ports if firewall is active) ---
if command -v firewall-cmd &>/dev/null && systemctl is-active firewalld &>/dev/null; then
echo " Configuring firewalld..."
for port in 3000 8001 8002 8003 8004 9000 9001 11434; do
sudo firewall-cmd --permanent --add-port="${port}/tcp" 2>/dev/null || true
done
sudo firewall-cmd --reload 2>/dev/null || true
echo " ✓ Firewall ports opened"
elif command -v ufw &>/dev/null && sudo ufw status 2>/dev/null | grep -q "active"; then
echo " Configuring ufw..."
for port in 3000 8001 8002 8003 8004 9000 9001 11434; do
sudo ufw allow "${port}/tcp" 2>/dev/null || true
done
echo " ✓ UFW ports opened"
fi
REMOTE_SCRIPT
echo ""
# -------------------------------------------------------
# Step 1: Clone or update the repo on the remote host
# -------------------------------------------------------
echo "--- Step 1: Syncing repository ---"
ssh "$REMOTE_HOST" bash -s -- "$REMOTE_DIR" "$REPO_URL" <<'REMOTE_SCRIPT'
set -euo pipefail
REMOTE_DIR="$1"
REPO_URL="$2"
if [ -d "$REMOTE_DIR/.git" ]; then
echo " Updating existing repo..."
cd "$REMOTE_DIR"
git fetch origin
git reset --hard origin/main
else
echo " Cloning fresh..."
git clone "$REPO_URL" "$REMOTE_DIR"
cd "$REMOTE_DIR"
fi
echo " ✓ Repo synced at $(git log --oneline -1)"
REMOTE_SCRIPT
echo ""
# -------------------------------------------------------
# Step 2: Detect or configure Ollama
# -------------------------------------------------------
echo "--- Step 2: Configuring Ollama ---"
# Always use the Docker Ollama container with GPU passthrough
# The ollama/ollama image ships with CUDA runtime built-in
USE_DOCKER_OLLAMA=true
OLLAMA_URL="http://ollama:11434"
echo " Using Docker Ollama container (GPU-accelerated via NVIDIA passthrough)"
echo " Host-accessible at localhost:11434"
echo ""
# -------------------------------------------------------
# Step 3: Create .env and compose override
# -------------------------------------------------------
echo "--- Step 3: Configuring environment ---"
ssh "$REMOTE_HOST" bash -s -- "$REMOTE_DIR" "$OLLAMA_URL" "$OLLAMA_MODEL" "$USE_DOCKER_OLLAMA" <<'REMOTE_SCRIPT'
set -euo pipefail
REMOTE_DIR="$1"
OLLAMA_URL="$2"
OLLAMA_MODEL="$3"
USE_DOCKER_OLLAMA="$4"
cd "$REMOTE_DIR"
# Read API keys from local files if they exist
POLYGON_KEY=""
ALPACA_KEY=""
ALPACA_SECRET=""
ALPACA_URL="https://paper-api.alpaca.markets"
[ -f polygon.io.key ] && POLYGON_KEY=$(cat polygon.io.key)
[ -f alpaca.key ] && ALPACA_KEY=$(cat alpaca.key)
[ -f alpaca.secret ] && ALPACA_SECRET=$(cat alpaca.secret)
[ -f alpaca.url ] && ALPACA_URL=$(cat alpaca.url)
cat > .env <<EOF
# Stonks Oracle — Docker Deployment Environment
MARKET_DATA_API_KEY=${POLYGON_KEY}
BROKER_API_KEY=${ALPACA_KEY}
BROKER_API_SECRET=${ALPACA_SECRET}
BROKER_BASE_URL=${ALPACA_URL}
TRADING_ENABLED=true
TRADING_RISK_TIER=moderate
TRADING_MAX_OPEN_POSITIONS=15
OLLAMA_MODEL=${OLLAMA_MODEL}
MACRO_ENABLED=true
COMPETITIVE_ENABLED=true
EOF
# Create compose override based on Ollama configuration
if [ "$USE_DOCKER_OLLAMA" = "true" ]; then
# Using Docker Ollama — no override needed, default compose handles it
rm -f docker-compose.override.yml
echo " ✓ Using Docker Ollama container"
else
# Using external Ollama — disable the container and point services to it
# Determine if URL is localhost (needs host-gateway) or remote
if echo "$OLLAMA_URL" | grep -qE "localhost|127\.0\.0\.1"; then
DOCKER_OLLAMA_URL="http://host.docker.internal:$(echo "$OLLAMA_URL" | grep -oP ':\K[0-9]+')"
cat > docker-compose.override.yml <<EOF
services:
ollama:
entrypoint: ["true"]
restart: "no"
ports: []
extractor:
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
environment:
OLLAMA_BASE_URL: "${DOCKER_OLLAMA_URL}"
extra_hosts:
- "host.docker.internal:host-gateway"
recommendation:
environment:
OLLAMA_BASE_URL: "${DOCKER_OLLAMA_URL}"
extra_hosts:
- "host.docker.internal:host-gateway"
EOF
else
# Remote Ollama — containers can reach it directly
cat > docker-compose.override.yml <<EOF
services:
ollama:
entrypoint: ["true"]
restart: "no"
extractor:
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
environment:
OLLAMA_BASE_URL: "${OLLAMA_URL}"
recommendation:
environment:
OLLAMA_BASE_URL: "${OLLAMA_URL}"
EOF
fi
echo " ✓ Override created — services will use external Ollama at ${OLLAMA_URL}"
fi
echo " ✓ .env configured (polygon=$([ -n "$POLYGON_KEY" ] && echo 'set' || echo 'empty'), alpaca=$([ -n "$ALPACA_KEY" ] && echo 'set' || echo 'empty'))"
REMOTE_SCRIPT
echo ""
# -------------------------------------------------------
# Step 4: Build and start all services
# -------------------------------------------------------
echo "--- Step 4: Building and starting services ---"
ssh "$REMOTE_HOST" bash -s -- "$REMOTE_DIR" "$USE_DOCKER_OLLAMA" <<'REMOTE_SCRIPT'
set -euo pipefail
REMOTE_DIR="$1"
USE_DOCKER_OLLAMA="$2"
cd "$REMOTE_DIR"
# Stop any existing deployment
docker compose down 2>/dev/null || true
# Build all images
echo " Building images (this may take a few minutes)..."
docker compose build --quiet 2>&1 | tail -5
# Start infrastructure
echo " Starting infrastructure..."
if [ "$USE_DOCKER_OLLAMA" = "true" ]; then
docker compose up -d postgres redis minio minio-init ollama
else
docker compose up -d postgres redis minio minio-init
fi
# Wait for infrastructure to be healthy
echo " Waiting for infrastructure health checks..."
for svc in postgres redis minio; do
for i in $(seq 1 30); do
if docker compose ps "$svc" 2>/dev/null | grep -q healthy; then
break
fi
sleep 2
done
done
echo " ✓ Infrastructure healthy"
# Start all application services
echo " Starting application services..."
docker compose up -d
echo " Waiting for services to stabilize..."
sleep 20
# Show status
echo ""
echo " Service Status:"
docker compose ps --format "table {{.Name}}\t{{.Status}}" 2>/dev/null | head -25 || docker compose ps
REMOTE_SCRIPT
echo ""
# -------------------------------------------------------
# Step 5: Seed the database
# -------------------------------------------------------
echo "--- Step 5: Seeding database ---"
ssh "$REMOTE_HOST" bash -s -- "$REMOTE_DIR" <<'REMOTE_SCRIPT'
set -euo pipefail
cd "$1"
# Wait for query-api to be healthy
for i in $(seq 1 30); do
if docker compose ps query-api 2>/dev/null | grep -q healthy; then
break
fi
sleep 3
done
# Run the symbol registry seed
echo " Seeding symbol registry..."
docker compose exec -T scheduler python -m services.symbol_registry.seed 2>/dev/null && echo " ✓ Database seeded" || echo " ⚠ Seed skipped (may already be seeded or service not ready)"
REMOTE_SCRIPT
echo ""
# -------------------------------------------------------
# Step 6: Ensure Ollama model is available
# -------------------------------------------------------
echo "--- Step 6: Checking Ollama model ---"
ssh "$REMOTE_HOST" bash -s -- "$OLLAMA_URL" "$OLLAMA_MODEL" "$USE_DOCKER_OLLAMA" "$REMOTE_DIR" <<'REMOTE_SCRIPT'
set -euo pipefail
OLLAMA_URL="$1"
OLLAMA_MODEL="$2"
USE_DOCKER_OLLAMA="$3"
REMOTE_DIR="$4"
if [ "$USE_DOCKER_OLLAMA" = "true" ]; then
# Pull via Docker container
cd "$REMOTE_DIR"
if docker compose exec -T ollama ollama list 2>/dev/null | grep -q "$OLLAMA_MODEL"; then
echo " ✓ Model $OLLAMA_MODEL already available"
else
echo " Pulling $OLLAMA_MODEL via Docker Ollama..."
docker compose exec -T ollama ollama pull "$OLLAMA_MODEL"
echo " ✓ Model pulled"
fi
else
# Check via API
if curl -sf "$OLLAMA_URL/api/tags" 2>/dev/null | grep -q "$OLLAMA_MODEL"; then
echo " ✓ Model $OLLAMA_MODEL already available at $OLLAMA_URL"
else
echo " Pulling $OLLAMA_MODEL via $OLLAMA_URL..."
curl -sf "$OLLAMA_URL/api/pull" -d "{\"name\":\"$OLLAMA_MODEL\"}" | tail -1
echo " ✓ Model pulled"
fi
fi
REMOTE_SCRIPT
echo ""
# -------------------------------------------------------
# Done
# -------------------------------------------------------
REMOTE_IP=$(echo "$REMOTE_HOST" | cut -d@ -f2)
echo "=== Deployment Complete ==="
echo ""
echo "Endpoints:"
echo " Dashboard: http://${REMOTE_IP}:3000"
echo " Query API: http://${REMOTE_IP}:8004"
echo " Symbol Registry: http://${REMOTE_IP}:8001"
echo " Trading Engine: http://${REMOTE_IP}:8002"
echo " Risk Engine: http://${REMOTE_IP}:8003"
echo " MinIO Console: http://${REMOTE_IP}:9001"
echo " Superset: http://${REMOTE_IP}:8088"
echo " Ollama: http://${REMOTE_IP}:11434"
echo ""
echo "Commands:"
echo " ssh $REMOTE_HOST 'cd $REMOTE_DIR && docker compose logs -f'"
echo " ssh $REMOTE_HOST 'cd $REMOTE_DIR && docker compose ps'"
echo " ssh $REMOTE_HOST 'cd $REMOTE_DIR && docker compose down'"