backend redesign

This commit is contained in:
2026-03-11 18:47:11 -04:00
parent 8ff277c8c6
commit e99ef5d2dd
210 changed files with 12147 additions and 155 deletions

17
bin/build-all Executable file
View File

@@ -0,0 +1,17 @@
#!/bin/bash
# Build all container images
set -e
DIR="$(cd "$(dirname "$0")" && pwd)"
echo "Building all container images..."
echo
"$DIR/build" flink "$@"
"$DIR/build" relay "$@"
"$DIR/build" ingestor "$@"
"$DIR/build" web "$@"
echo
echo "All images built successfully!"

128
bin/config-update Executable file
View File

@@ -0,0 +1,128 @@
#!/usr/bin/env bash
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
usage() {
echo "Usage: $0 [ENVIRONMENT] [CONFIG_NAME]"
echo ""
echo "Update Kubernetes ConfigMaps from YAML files"
echo ""
echo "Arguments:"
echo " ENVIRONMENT Target environment: dev or prod (default: dev)"
echo " CONFIG_NAME Specific config to update (optional, updates all if not specified)"
echo ""
echo "Available configs:"
echo " relay-config - ZMQ relay configuration"
echo " ingestor-config - CCXT ingestor configuration"
echo " flink-config - Flink job configuration"
echo ""
echo "Examples:"
echo " $0 # Update all dev configs"
echo " $0 dev # Update all dev configs"
echo " $0 dev relay-config # Update only relay-config in dev"
echo " $0 prod # Update all prod configs"
echo " $0 prod flink-config # Update only flink-config in prod"
exit 1
}
# Parse arguments
ENV="${1:-dev}"
CONFIG_NAME="${2:-}"
if [[ "$ENV" != "dev" && "$ENV" != "prod" ]]; then
echo -e "${RED}Error: Environment must be 'dev' or 'prod'${NC}"
usage
fi
CONFIG_DIR="$ROOT_DIR/deploy/k8s/$ENV/configs"
if [ ! -d "$CONFIG_DIR" ]; then
echo -e "${RED}Error: Config directory not found: $CONFIG_DIR${NC}"
exit 1
fi
# Get kubectl context
if [[ "$ENV" == "prod" ]]; then
CONTEXT=$(kubectl config current-context)
echo -e "${YELLOW}⚠️ WARNING: Updating PRODUCTION configs!${NC}"
echo -e "${YELLOW}Current kubectl context: $CONTEXT${NC}"
read -p "Are you sure you want to continue? (yes/no): " confirm
if [[ "$confirm" != "yes" ]]; then
echo "Aborted."
exit 0
fi
fi
apply_config() {
local config_name="$1"
local config_file="$CONFIG_DIR/$config_name.yaml"
if [ ! -f "$config_file" ]; then
echo -e "${RED}✗ Config file not found: $config_file${NC}"
return 1
fi
echo -e "${GREEN}→${NC} Creating/updating ConfigMap $config_name..."
kubectl create configmap "$config_name" \
--from-file=config.yaml="$config_file" \
--dry-run=client -o yaml | kubectl apply -f -
echo -e "${GREEN}✓${NC} $config_name updated"
# Optionally restart pods that use this config
local restart_pods=""
case "$config_name" in
relay-config)
restart_pods="deployment/relay"
;;
ingestor-config)
restart_pods="deployment/ingestor"
;;
flink-config)
restart_pods="deployment/flink-jobmanager deployment/flink-taskmanager"
;;
esac
if [ -n "$restart_pods" ]; then
echo -e "${YELLOW} Restarting pods...${NC}"
kubectl rollout restart $restart_pods 2>/dev/null || echo -e "${YELLOW} (No pods found to restart)${NC}"
fi
}
# Update specific config or all configs
if [ -n "$CONFIG_NAME" ]; then
# Update single config
apply_config "$CONFIG_NAME"
else
# Update all configs
echo -e "${GREEN}Updating all $ENV configs...${NC}"
echo ""
CONFIGS=(
"relay-config"
"ingestor-config"
"flink-config"
)
FAILED=0
for config in "${CONFIGS[@]}"; do
if ! apply_config "$config"; then
FAILED=$((FAILED + 1))
fi
done
echo ""
if [ $FAILED -gt 0 ]; then
echo -e "${YELLOW}⚠️ $FAILED config(s) failed to apply${NC}"
exit 1
else
echo -e "${GREEN}✓ All configs updated successfully${NC}"
fi
fi

View File

@@ -1,11 +1,11 @@
#!/bin/bash
#REMOTE=northamerica-northeast2-docker.pkg.dev/dexorder-430504/dexorder
REMOTE=git.dxod.org/dexorder/dexorder
REMOTE=${REMOTE:-git.dxod.org/dexorder/dexorder}
if [ "$1" != "backend" ] && [ "$1" != "web" ]; then
if [ "$1" != "flink" ] && [ "$1" != "relay" ] && [ "$1" != "ingestor" ] && [ "$1" != "web" ]; then
echo
echo usage: "$0 "'{backend|web} [''dev''] [config] [deployment] [kubernetes] [image_tag]'
echo usage: "$0 "'{flink|relay|ingestor|web} [''dev''] [config] [deployment] [kubernetes] [image_tag]'
echo
echo ' [''dev''] if the literal string ''dev'' is not the second argument, then the build refuses to run if source code is not checked in. Otherwise, the git revision numbers are used in the image tag.'
echo
@@ -86,14 +86,21 @@ fi
if [ "$DEPLOY" == "0" ]; then
ACTION=Building
NO_CACHE=--no-cache
#NO_CACHE=--no-cache
else
ACTION=Making
fi
echo $ACTION $PROJECT config=$CONFIG deployment=$DEPLOYMENT '=>' $TAG
docker build $NO_CACHE -f deploy/Dockerfile-$PROJECT --build-arg="CONFIG=$CONFIG" --build-arg="DEPLOYMENT=$DEPLOYMENT" -t dexorder/ai-$PROJECT:latest . || exit 1
# Copy protobuf definitions into project directory for Docker build
# Using rsync --checksum so unchanged files keep their timestamps (preserves docker layer cache)
rsync -a --checksum --delete protobuf/ $PROJECT/protobuf/
docker build $NO_CACHE -f $PROJECT/Dockerfile --build-arg="CONFIG=$CONFIG" --build-arg="DEPLOYMENT=$DEPLOYMENT" -t dexorder/ai-$PROJECT:latest $PROJECT || exit 1
# Cleanup is handled by trap
docker tag dexorder/ai-$PROJECT:latest dexorder/ai-$PROJECT:$TAG
docker tag dexorder/ai-$PROJECT:$TAG $REMOTE/ai-$PROJECT:$TAG
docker tag $REMOTE/ai-$PROJECT:$TAG $REMOTE/ai-$PROJECT:latest
@@ -105,7 +112,7 @@ echo "$(date)" built $REMOTE/ai-$PROJECT:$TAG
if [ "$DEPLOY" == "1" ]; then
docker push $REMOTE/ai-$PROJECT:$TAG
YAML=$(sed "s#image: dexorder/ai-$PROJECT*#image: $REMOTE/ai-$PROJECT:$TAG#" deploy/$KUBERNETES.yaml)
YAML=$(sed "s#image: dexorder/ai-$PROJECT*#image: $REMOTE/ai-$PROJECT:$TAG#" deploy/k8s/$KUBERNETES.yaml)
echo "$YAML" | kubectl apply -f - || echo "$YAML" "\nkubectl apply failed" && exit 1
echo deployed $KUBERNETES.yaml $REMOTE/ai-$PROJECT:$TAG
fi

364
bin/dev Executable file
View File

@@ -0,0 +1,364 @@
#!/usr/bin/env bash
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
usage() {
echo "Usage: $0 [COMMAND]"
echo ""
echo "Manage the minikube development environment"
echo ""
echo "Commands:"
echo " start Start minikube and deploy all services"
echo " stop Stop minikube"
echo " restart [svc] Rebuild and redeploy all services, or just one (relay|ingestor|flink)"
echo " rebuild [svc] Rebuild all custom images, or just one"
echo " deploy [svc] Deploy/update all services, or just one"
echo " status Show status of all services"
echo " logs Tail logs for a service"
echo " shell Open a shell in a service pod"
echo " clean Delete all resources and volumes"
echo " tunnel Start minikube tunnel (for LoadBalancer access)"
echo ""
echo "Examples:"
echo " $0 start # Start minikube and deploy everything"
echo " $0 rebuild # Rebuild all custom images"
echo " $0 logs relay # Tail logs for relay service"
echo " $0 shell ingestor # Open shell in ingestor pod"
exit 1
}
COMMAND="${1:-start}"
check_minikube() {
if ! command -v minikube &> /dev/null; then
echo -e "${RED}Error: minikube not found. Please install minikube first.${NC}"
echo "https://minikube.sigs.k8s.io/docs/start/"
exit 1
fi
}
check_kubectl() {
if ! command -v kubectl &> /dev/null; then
echo -e "${RED}Error: kubectl not found. Please install kubectl first.${NC}"
exit 1
fi
}
start_minikube() {
echo -e "${BLUE}Starting minikube...${NC}"
if minikube status &> /dev/null; then
echo -e "${GREEN}✓ Minikube already running${NC}"
else
minikube start --cpus=6 --memory=12g --driver=docker
echo -e "${GREEN}✓ Minikube started${NC}"
fi
# Enable ingress addon
echo -e "${BLUE}Enabling ingress addon...${NC}"
minikube addons enable ingress
echo -e "${GREEN}✓ Ingress enabled${NC}"
# Set docker environment
echo -e "${YELLOW}Setting docker environment to minikube...${NC}"
eval $(minikube docker-env)
echo -e "${GREEN}✓ Docker environment set${NC}"
# Add /etc/hosts entry
MINIKUBE_IP=$(minikube ip)
if ! grep -q "dexorder.local" /etc/hosts; then
echo -e "${YELLOW}Adding dexorder.local to /etc/hosts (requires sudo)...${NC}"
echo "$MINIKUBE_IP dexorder.local" | sudo tee -a /etc/hosts
else
echo -e "${GREEN}✓ /etc/hosts entry exists${NC}"
fi
}
rebuild_images() {
local service="${1:-all}"
echo -e "${BLUE}Building custom images...${NC}"
# Use minikube's docker daemon
eval $(minikube docker-env)
# Build images using the standard bin/build script with dev flag
cd "$ROOT_DIR"
# Load existing tags so we preserve whichever services we're not rebuilding
if [ -f "$ROOT_DIR/.dev-image-tag" ]; then
source "$ROOT_DIR/.dev-image-tag"
fi
# Helper: run build, show output, and return just the dev tag via stdout
# Build output goes to stderr so the caller can capture only the tag via $()
build_and_get_tag() {
local svc="$1"
local output
output=$("$SCRIPT_DIR/build" "$svc" dev 2>&1) || { echo "$output" >&2; return 1; }
echo "$output" >&2
# Extract tag from "built <remote>/ai-<svc>:<tag>" line
echo "$output" | grep -oE "ai-${svc}:dev[0-9]+" | tail -1 | cut -d: -f2
}
if [ "$service" == "all" ] || [ "$service" == "relay" ]; then
echo -e "${GREEN}→${NC} Building relay..."
RELAY_TAG=$(build_and_get_tag relay) || exit 1
docker tag "dexorder/ai-relay:$RELAY_TAG" "dexorder/relay:$RELAY_TAG"
fi
if [ "$service" == "all" ] || [ "$service" == "ingestor" ]; then
echo -e "${GREEN}→${NC} Building ingestor..."
INGEST_TAG=$(build_and_get_tag ingestor) || exit 1
docker tag "dexorder/ai-ingestor:$INGEST_TAG" "dexorder/ingestor:$INGEST_TAG"
fi
if [ "$service" == "all" ] || [ "$service" == "flink" ]; then
echo -e "${GREEN}→${NC} Building flink..."
FLINK_TAG=$(build_and_get_tag flink) || exit 1
docker tag "dexorder/ai-flink:$FLINK_TAG" "dexorder/flink:$FLINK_TAG"
fi
# Save the tags for deployment (all three, preserving any we didn't rebuild)
echo "RELAY_TAG=$RELAY_TAG" > "$ROOT_DIR/.dev-image-tag"
echo "INGEST_TAG=$INGEST_TAG" >> "$ROOT_DIR/.dev-image-tag"
echo "FLINK_TAG=$FLINK_TAG" >> "$ROOT_DIR/.dev-image-tag"
echo -e "${GREEN}✓ Images built: relay=$RELAY_TAG, ingestor=$INGEST_TAG, flink=$FLINK_TAG${NC}"
}
deploy_services() {
echo -e "${BLUE}Deploying services to minikube...${NC}"
cd "$ROOT_DIR"
# Get the dev image tags
if [ -f "$ROOT_DIR/.dev-image-tag" ]; then
source "$ROOT_DIR/.dev-image-tag"
echo -e "${BLUE}Using image tags:${NC}"
echo -e " Relay: $RELAY_TAG"
echo -e " Ingestor: $INGEST_TAG"
echo -e " Flink: $FLINK_TAG"
else
echo -e "${YELLOW}⚠️ No dev tags found. Using 'latest'. Run rebuild first.${NC}"
RELAY_TAG="latest"
INGEST_TAG="latest"
FLINK_TAG="latest"
fi
# Create secrets first (if they exist)
echo -e "${GREEN}→${NC} Checking secrets..."
if ls deploy/k8s/dev/secrets/*.yaml &> /dev/null; then
"$SCRIPT_DIR/secret-update" dev || echo -e "${YELLOW} (Some secrets missing - copy from .example files)${NC}"
else
echo -e "${YELLOW}⚠️ No secrets found. Copy from .example files:${NC}"
echo -e "${YELLOW} cd deploy/k8s/dev/secrets${NC}"
echo -e "${YELLOW} cp ai-secrets.yaml.example ai-secrets.yaml${NC}"
echo -e "${YELLOW} # Edit with actual values, then run: bin/secret-update dev${NC}"
fi
# Update configs
echo -e "${GREEN}→${NC} Updating configs..."
"$SCRIPT_DIR/config-update" dev
# Apply kustomize with image tag substitution
echo -e "${GREEN}→${NC} Applying Kubernetes manifests..."
kubectl kustomize deploy/k8s/dev/ | \
sed "s|image: dexorder/flink:latest|image: dexorder/flink:$FLINK_TAG|g" | \
sed "s|image: dexorder/relay:latest|image: dexorder/relay:$RELAY_TAG|g" | \
sed "s|image: dexorder/ingestor:latest|image: dexorder/ingestor:$INGEST_TAG|g" | \
kubectl apply -f -
echo -e "${GREEN}✓ Services deployed${NC}"
echo ""
echo -e "${BLUE}Waiting for deployments to be ready...${NC}"
kubectl wait --for=condition=available --timeout=300s \
deployment/relay \
deployment/ingestor \
deployment/iceberg-catalog \
deployment/flink-jobmanager \
deployment/flink-taskmanager \
2>/dev/null || echo -e "${YELLOW}(Some deployments not ready yet)${NC}"
echo ""
echo -e "${GREEN}✓ Dev environment ready!${NC}"
echo ""
echo -e "${BLUE}Access the application:${NC}"
echo -e " Web UI: http://dexorder.local/cryptochimp/"
echo -e " Backend WS: ws://dexorder.local/ws"
echo ""
echo -e "${BLUE}Admin UIs (use port-forward):${NC}"
echo -e " Flink UI: kubectl port-forward svc/flink-jobmanager 8081:8081"
echo -e " Then open http://localhost:8081"
echo -e " MinIO Console: kubectl port-forward svc/minio 9001:9001"
echo -e " Then open http://localhost:9001"
echo ""
echo -e "${YELLOW}Note: Run 'minikube tunnel' in another terminal for dexorder.local ingress to work${NC}"
}
show_status() {
echo -e "${BLUE}Kubernetes Resources:${NC}"
echo ""
kubectl get pods,svc,ingress
}
show_logs() {
local service="$1"
if [ -z "$service" ]; then
echo -e "${RED}Error: Please specify a service name${NC}"
echo "Available services: relay, ingestor, flink-jobmanager, flink-taskmanager, kafka, postgres, minio, iceberg-catalog"
exit 1
fi
# Try to find pod by label or name
local pod=$(kubectl get pods -l app="$service" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null)
if [ -z "$pod" ]; then
pod=$(kubectl get pods | grep "$service" | head -n1 | awk '{print $1}')
fi
if [ -z "$pod" ]; then
echo -e "${RED}Error: No pod found for service '$service'${NC}"
exit 1
fi
echo -e "${BLUE}Tailing logs for $pod...${NC}"
kubectl logs -f "$pod"
}
open_shell() {
local service="$1"
if [ -z "$service" ]; then
echo -e "${RED}Error: Please specify a service name${NC}"
exit 1
fi
local pod=$(kubectl get pods -l app="$service" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null)
if [ -z "$pod" ]; then
pod=$(kubectl get pods | grep "$service" | head -n1 | awk '{print $1}')
fi
if [ -z "$pod" ]; then
echo -e "${RED}Error: No pod found for service '$service'${NC}"
exit 1
fi
echo -e "${BLUE}Opening shell in $pod...${NC}"
kubectl exec -it "$pod" -- /bin/sh || kubectl exec -it "$pod" -- /bin/bash
}
clean_all() {
echo -e "${RED}⚠️ WARNING: This will delete all resources and volumes!${NC}"
read -p "Are you sure? (yes/no): " confirm
if [[ "$confirm" != "yes" ]]; then
echo "Aborted."
exit 0
fi
echo -e "${BLUE}Deleting all resources...${NC}"
kubectl delete -k deploy/k8s/dev/ || true
kubectl delete pvc --all || true
echo -e "${GREEN}✓ Resources deleted${NC}"
}
start_tunnel() {
echo -e "${BLUE}Starting minikube tunnel...${NC}"
echo -e "${YELLOW}This requires sudo and will run in the foreground.${NC}"
echo -e "${YELLOW}Press Ctrl+C to stop.${NC}"
echo ""
minikube tunnel
}
# Deploy a single service using kubectl set image with the dev tag (never uses 'latest')
deploy_service() {
local service="$1"
if [ -f "$ROOT_DIR/.dev-image-tag" ]; then
source "$ROOT_DIR/.dev-image-tag"
fi
local image
case "$service" in
relay) image="dexorder/relay:$RELAY_TAG" ;;
ingestor) image="dexorder/ingestor:$INGEST_TAG" ;;
flink) image="dexorder/flink:$FLINK_TAG" ;;
*)
echo -e "${RED}Unknown service: $service. Use relay, ingestor, or flink.${NC}"
exit 1
;;
esac
echo -e "${GREEN}→${NC} Deploying $service with image $image..."
case "$service" in
flink)
kubectl set image deployment/flink-jobmanager flink-jobmanager=$image
kubectl set image deployment/flink-taskmanager flink-taskmanager=$image
;;
*)
kubectl set image deployment/$service $service=$image
;;
esac
echo -e "${GREEN}✓ $service updated to $image${NC}"
}
# Main command routing
check_minikube
check_kubectl
case "$COMMAND" in
start)
start_minikube
rebuild_images
deploy_services
;;
stop)
echo -e "${BLUE}Stopping minikube...${NC}"
minikube stop
echo -e "${GREEN}✓ Minikube stopped${NC}"
;;
restart)
if [ -n "$2" ]; then
rebuild_images "$2"
deploy_service "$2"
else
rebuild_images
deploy_services
fi
;;
rebuild)
rebuild_images "${2:-}"
;;
deploy)
if [ -n "$2" ]; then
deploy_service "$2"
else
deploy_services
fi
;;
status)
show_status
;;
logs)
show_logs "$2"
;;
shell)
open_shell "$2"
;;
clean)
clean_all
;;
tunnel)
start_tunnel
;;
*)
usage
;;
esac

117
bin/secret-update Executable file
View File

@@ -0,0 +1,117 @@
#!/usr/bin/env bash
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
usage() {
echo "Usage: $0 [ENVIRONMENT] [SECRET_NAME]"
echo ""
echo "Update Kubernetes secrets from YAML files"
echo ""
echo "Arguments:"
echo " ENVIRONMENT Target environment: dev or prod (default: dev)"
echo " SECRET_NAME Specific secret to update (optional, updates all if not specified)"
echo ""
echo "Available secrets:"
echo " ai-secrets - AI backend API keys"
echo " postgres-secret - PostgreSQL password"
echo " minio-secret - MinIO credentials"
echo " ingestor-secrets - Exchange API keys"
echo ""
echo "Examples:"
echo " $0 # Update all dev secrets"
echo " $0 dev # Update all dev secrets"
echo " $0 dev ai-secrets # Update only ai-secrets in dev"
echo " $0 prod # Update all prod secrets"
echo " $0 prod minio-secret # Update only minio-secret in prod"
exit 1
}
# Parse arguments
ENV="${1:-dev}"
SECRET_NAME="${2:-}"
if [[ "$ENV" != "dev" && "$ENV" != "prod" ]]; then
echo -e "${RED}Error: Environment must be 'dev' or 'prod'${NC}"
usage
fi
SECRETS_DIR="$ROOT_DIR/deploy/k8s/$ENV/secrets"
if [ ! -d "$SECRETS_DIR" ]; then
echo -e "${RED}Error: Secrets directory not found: $SECRETS_DIR${NC}"
exit 1
fi
# Get kubectl context
if [[ "$ENV" == "prod" ]]; then
CONTEXT=$(kubectl config current-context)
echo -e "${YELLOW}⚠️ WARNING: Updating PRODUCTION secrets!${NC}"
echo -e "${YELLOW}Current kubectl context: $CONTEXT${NC}"
read -p "Are you sure you want to continue? (yes/no): " confirm
if [[ "$confirm" != "yes" ]]; then
echo "Aborted."
exit 0
fi
fi
apply_secret() {
local secret_file="$1"
local secret_basename=$(basename "$secret_file" .yaml)
if [ ! -f "$secret_file" ]; then
echo -e "${RED}✗ Secret file not found: $secret_file${NC}"
echo -e "${YELLOW} Copy from ${secret_basename}.yaml.example and fill in values${NC}"
return 1
fi
echo -e "${GREEN}→${NC} Applying $secret_basename..."
kubectl apply -f "$secret_file"
echo -e "${GREEN}✓${NC} $secret_basename updated"
}
# Update specific secret or all secrets
if [ -n "$SECRET_NAME" ]; then
# Update single secret
SECRET_FILE="$SECRETS_DIR/$SECRET_NAME.yaml"
apply_secret "$SECRET_FILE"
else
# Update all secrets
echo -e "${GREEN}Updating all $ENV secrets...${NC}"
echo ""
SECRETS=(
"ai-secrets"
"postgres-secret"
"minio-secret"
"ingestor-secrets"
"flink-secrets"
)
FAILED=0
for secret in "${SECRETS[@]}"; do
SECRET_FILE="$SECRETS_DIR/$secret.yaml"
if ! apply_secret "$SECRET_FILE"; then
FAILED=$((FAILED + 1))
fi
done
echo ""
if [ $FAILED -gt 0 ]; then
echo -e "${YELLOW}⚠️ $FAILED secret(s) failed to apply${NC}"
echo -e "${YELLOW}Create missing secret files by copying from .example templates:${NC}"
echo -e "${YELLOW} cd $SECRETS_DIR${NC}"
echo -e "${YELLOW} cp SECRET_NAME.yaml.example SECRET_NAME.yaml${NC}"
echo -e "${YELLOW} # Edit SECRET_NAME.yaml with actual values${NC}"
exit 1
else
echo -e "${GREEN}✓ All secrets updated successfully${NC}"
fi
fi