Update to match Dockerfile changes and bypass Docker BuildKit secret caching issues. Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
388 lines
14 KiB
Bash
Executable File
388 lines
14 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════
|
|
# ByteLyst Investment Trading - Production Deployment Script
|
|
# ═══════════════════════════════════════════════════════════════════════
|
|
# Usage: ./deploy-invttrdg.sh [--force] [--skip-health-check] [--no-cache]
|
|
# (No arguments = interactive menu)
|
|
#
|
|
# What it does:
|
|
# 1. Dirty check: uncommitted changes, unpushed commits
|
|
# 2. Pull and rebase origin/main
|
|
# 3. Check @bytelyst package publication
|
|
# 4. Build and deploy Docker containers
|
|
# 5. Verify endpoints: https://api.bytelyst.com/invttrdg, https://invttrdg.bytelyst.com
|
|
#
|
|
# Options:
|
|
# --force Skip dirty checks and force deployment
|
|
# --skip-health-check Skip endpoint health verification
|
|
# --no-cache Build Docker images without cache
|
|
#
|
|
# Interactive Menu (no arguments):
|
|
# 1 - Normal deployment (with cache, with health checks)
|
|
# 2 - Force deployment (skip dirty checks, with cache)
|
|
# 3 - Skip health checks (with cache)
|
|
# 4 - No-cache build (force rebuild, with health checks)
|
|
# 5 - Force + No-cache (skip checks, force rebuild)
|
|
# 6 - Force + Skip health checks (skip both)
|
|
# 7 - All options: Force + Skip health + No-cache
|
|
# ═══════════════════════════════════════════════════════════════════════
|
|
|
|
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m'
|
|
|
|
log() { echo -e "${CYAN}[$(date +%H:%M:%S)]${NC} $*"; }
|
|
ok() { echo -e "${GREEN}[$(date +%H:%M:%S)] ✓${NC} $*"; }
|
|
warn() { echo -e "${YELLOW}[$(date +%H:%M:%S)] ⚠${NC} $*"; }
|
|
fail() { echo -e "${RED}[$(date +%H:%M:%S)] ✗${NC} $*"; exit 1; }
|
|
|
|
# ── Configuration ────────────────────────────────────────────────────
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
REPO_DIR="${SCRIPT_DIR}/../learning_ai_invt_trdg"
|
|
cd "$SCRIPT_DIR"
|
|
|
|
FORCE=false
|
|
SKIP_HEALTH_CHECK=false
|
|
NO_CACHE=false
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--force) FORCE=true; shift ;;
|
|
--skip-health-check) SKIP_HEALTH_CHECK=true; shift ;;
|
|
--no-cache) NO_CACHE=true; shift ;;
|
|
*) fail "Unknown option: $1" ;;
|
|
esac
|
|
done
|
|
|
|
# ── Interactive Menu ────────────────────────────────────────────────────
|
|
if [ $# -eq 0 ]; then
|
|
echo ""
|
|
echo -e "${CYAN}╔═══════════════════════════════════════════════════════════════╗${NC}"
|
|
echo -e "${CYAN}║${NC} ByteLyst Investment Trading - Deployment Options ${CYAN}║${NC}"
|
|
echo -e "${CYAN}╚═══════════════════════════════════════════════════════════════╝${NC}"
|
|
echo ""
|
|
echo -e " ${GREEN}1${NC} - Normal deployment (with cache, with health checks)"
|
|
echo -e " ${GREEN}2${NC} - Force deployment (skip dirty checks, with cache)"
|
|
echo -e " ${GREEN}3${NC} - Skip health checks (with cache)"
|
|
echo -e " ${GREEN}4${NC} - No-cache build (force rebuild, with health checks)"
|
|
echo -e " ${GREEN}5${NC} - Force + No-cache (skip checks, force rebuild)"
|
|
echo -e " ${GREEN}6${NC} - Force + Skip health checks (skip both)"
|
|
echo -e " ${GREEN}7${NC} - All options: Force + Skip health + No-cache"
|
|
echo ""
|
|
read -r -p "Select option [1-7]: " choice
|
|
|
|
case "$choice" in
|
|
1)
|
|
# Normal deployment (defaults)
|
|
;;
|
|
2)
|
|
FORCE=true
|
|
;;
|
|
3)
|
|
SKIP_HEALTH_CHECK=true
|
|
;;
|
|
4)
|
|
NO_CACHE=true
|
|
;;
|
|
5)
|
|
FORCE=true
|
|
NO_CACHE=true
|
|
;;
|
|
6)
|
|
FORCE=true
|
|
SKIP_HEALTH_CHECK=true
|
|
;;
|
|
7)
|
|
FORCE=true
|
|
SKIP_HEALTH_CHECK=true
|
|
NO_CACHE=true
|
|
;;
|
|
*)
|
|
fail "Invalid option. Please run again and select 1-7."
|
|
;;
|
|
esac
|
|
|
|
echo ""
|
|
log "Selected configuration:"
|
|
[ "$FORCE" = true ] && echo " - Force deployment (skip dirty checks)"
|
|
[ "$SKIP_HEALTH_CHECK" = true ] && echo " - Skip health checks"
|
|
[ "$NO_CACHE" = true ] && echo " - No-cache build (force rebuild)"
|
|
[ "$FORCE" = false ] && [ "$SKIP_HEALTH_CHECK" = false ] && [ "$NO_CACHE" = false ] && echo " - Normal deployment"
|
|
echo ""
|
|
read -r -p "Press Enter to continue or Ctrl+C to cancel..."
|
|
echo ""
|
|
fi
|
|
|
|
# ── Prerequisites ────────────────────────────────────────────────────
|
|
if [ ! -d "$REPO_DIR" ]; then
|
|
fail "Repo directory not found: $REPO_DIR"
|
|
fi
|
|
|
|
cd "$REPO_DIR"
|
|
|
|
# ── Dirty Check ───────────────────────────────────────────────────────
|
|
if [ "$FORCE" = false ]; then
|
|
log "Running dirty checks..."
|
|
|
|
# Check for uncommitted changes
|
|
if ! git diff-index --quiet HEAD --; then
|
|
fail "Uncommitted changes detected. Commit or stash first, or use --force"
|
|
fi
|
|
|
|
# Check for untracked files
|
|
if [ -n "$(git ls-files --others --exclude-standard)" ]; then
|
|
fail "Untracked files detected. Commit or remove them, or use --force"
|
|
fi
|
|
|
|
# Check for unpushed commits
|
|
LOCAL_COMMIT=$(git rev-parse @)
|
|
REMOTE_COMMIT=$(git rev-parse '@{u}' 2>/dev/null || echo "")
|
|
|
|
if [ -n "$REMOTE_COMMIT" ] && [ "$LOCAL_COMMIT" != "$REMOTE_COMMIT" ]; then
|
|
fail "Unpushed commits detected. Push first or use --force"
|
|
fi
|
|
|
|
ok "Dirty checks passed"
|
|
else
|
|
warn "Skipping dirty checks (--force enabled)"
|
|
fi
|
|
|
|
# ── Pull and Rebase ───────────────────────────────────────────────────
|
|
log "Pulling latest changes from origin/main..."
|
|
git fetch origin
|
|
|
|
LOCAL_MAIN=$(git rev-parse main)
|
|
REMOTE_MAIN=$(git rev-parse origin/main)
|
|
|
|
if [ "$LOCAL_MAIN" != "$REMOTE_MAIN" ]; then
|
|
log "Local main is behind origin/main, rebasing..."
|
|
git rebase origin/main || {
|
|
fail "Rebase failed. Resolve conflicts and run: git rebase --continue"
|
|
}
|
|
ok "Rebase completed successfully"
|
|
else
|
|
ok "Already up to date with origin/main"
|
|
fi
|
|
|
|
# ── Run Smoke Tests ─────────────────────────────────────────────────────
|
|
if [ "$SKIP_HEALTH_CHECK" = false ]; then
|
|
log "Running smoke tests before deployment..."
|
|
if [ -f "scripts/smoke-release.sh" ]; then
|
|
chmod +x scripts/smoke-release.sh
|
|
./scripts/smoke-release.sh || {
|
|
fail "Smoke tests failed. Fix issues before deploying or use --skip-health-check"
|
|
}
|
|
ok "Smoke tests passed"
|
|
else
|
|
warn "Smoke test script not found, skipping pre-deployment tests"
|
|
fi
|
|
fi
|
|
|
|
# ── Check Package Publication ──────────────────────────────────────────
|
|
log "Checking @bytelyst package publication..."
|
|
|
|
# Check if required packages are published to Gitea registry
|
|
GITEA_REGISTRY="http://localhost:3300/api/packages/bytelyst/npm/"
|
|
REQUIRED_PACKAGES=(
|
|
"@bytelyst/config"
|
|
"@bytelyst/cosmos"
|
|
"@bytelyst/auth"
|
|
"@bytelyst/llm"
|
|
"@bytelyst/telemetry-client"
|
|
"@bytelyst/devops"
|
|
"@bytelyst/errors"
|
|
"@bytelyst/logger"
|
|
)
|
|
|
|
MISSING_PACKAGES=()
|
|
|
|
for package in "${REQUIRED_PACKAGES[@]}"; do
|
|
# Check if package is published (Gitea API returns 200 if package exists)
|
|
if ! curl -sf "${GITEA_REGISTRY}${package}" > /dev/null 2>&1; then
|
|
MISSING_PACKAGES+=("$package")
|
|
fi
|
|
done
|
|
|
|
if [ ${#MISSING_PACKAGES[@]} -gt 0 ]; then
|
|
fail "Required @bytelyst packages not published to Gitea registry:
|
|
${MISSING_PACKAGES[*]}
|
|
|
|
Please publish the packages first by running:
|
|
python3 /opt/bytelyst/republish_packages.py
|
|
|
|
This will publish all @bytelyst/* packages to https://gitea.bytelyst.com/"
|
|
fi
|
|
|
|
ok "All required @bytelyst packages are published"
|
|
|
|
# ── Build and Deploy ──────────────────────────────────────────────────
|
|
log "Building and deploying Docker containers..."
|
|
|
|
# Check if docker-compose files exist
|
|
if [ ! -f "docker-compose.yml" ]; then
|
|
fail "docker-compose.yml not found in $REPO_DIR"
|
|
fi
|
|
|
|
# Build and start services
|
|
log "Building Docker images..."
|
|
if [ "$NO_CACHE" = true ]; then
|
|
warn "Building without cache (--no-cache enabled)"
|
|
else
|
|
log "Building with cache (use --no-cache to force rebuild)"
|
|
fi
|
|
|
|
# Resolve Gitea npm token for BuildKit secret
|
|
if [ -z "${GITEA_NPM_TOKEN:-}" ]; then
|
|
if [ -f "/opt/bytelyst/.gitea_token" ]; then
|
|
GITEA_NPM_TOKEN=$(cat /opt/bytelyst/.gitea_token)
|
|
export GITEA_NPM_TOKEN
|
|
elif [ -f "$HOME/.gitea_npm_token" ]; then
|
|
GITEA_NPM_TOKEN=$(cat "$HOME/.gitea_npm_token")
|
|
export GITEA_NPM_TOKEN
|
|
else
|
|
fail "GITEA_NPM_TOKEN not set and no token file found"
|
|
fi
|
|
fi
|
|
|
|
# Collect build metadata (consumed by @bytelyst/devops)
|
|
# Declare separately so set -e + $(…) don't mask non-zero exit codes (SC2155).
|
|
BYTELYST_COMMIT_SHA=
|
|
BYTELYST_COMMIT_SHA_FULL=
|
|
BYTELYST_BRANCH=
|
|
BYTELYST_BUILT_AT=
|
|
BYTELYST_COMMIT_AUTHOR=
|
|
BYTELYST_COMMIT_MESSAGE=
|
|
BYTELYST_COMMIT_SHA=$(git rev-parse --short HEAD 2>/dev/null || echo unknown)
|
|
BYTELYST_COMMIT_SHA_FULL=$(git rev-parse HEAD 2>/dev/null || echo unknown)
|
|
BYTELYST_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo unknown)
|
|
BYTELYST_BUILT_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
BYTELYST_COMMIT_AUTHOR=$(git log -1 --pretty=format:'%an' 2>/dev/null || echo unknown)
|
|
BYTELYST_COMMIT_MESSAGE=$(git log -1 --pretty=format:'%s' 2>/dev/null | head -c 200 || echo unknown)
|
|
|
|
build_image() {
|
|
local dockerfile="$1"
|
|
local tag="$2"
|
|
local cache_flag=""
|
|
if [ "$NO_CACHE" = true ]; then
|
|
cache_flag="--no-cache"
|
|
fi
|
|
docker build --network host $cache_flag \
|
|
--build-arg "GITEA_NPM_TOKEN=${GITEA_NPM_TOKEN}" \
|
|
--build-arg "BYTELYST_COMMIT_SHA=${BYTELYST_COMMIT_SHA}" \
|
|
--build-arg "BYTELYST_COMMIT_SHA_FULL=${BYTELYST_COMMIT_SHA_FULL}" \
|
|
--build-arg "BYTELYST_BRANCH=${BYTELYST_BRANCH}" \
|
|
--build-arg "BYTELYST_BUILT_AT=${BYTELYST_BUILT_AT}" \
|
|
--build-arg "BYTELYST_COMMIT_AUTHOR=${BYTELYST_COMMIT_AUTHOR}" \
|
|
--build-arg "BYTELYST_COMMIT_MESSAGE=${BYTELYST_COMMIT_MESSAGE}" \
|
|
--build-arg "BYTELYST_DOCKER_IMAGE=${tag}" \
|
|
-f "$dockerfile" -t "$tag" .
|
|
}
|
|
|
|
build_image backend/Dockerfile invttrdg-backend:latest || fail "Backend build failed"
|
|
build_image web/Dockerfile invttrdg-web:latest || fail "Web build failed"
|
|
|
|
log "Starting services..."
|
|
docker compose up -d --no-build || fail "Docker compose up failed"
|
|
|
|
ok "Deployment completed"
|
|
|
|
# ── Health Check ──────────────────────────────────────────────────────
|
|
if [ "$SKIP_HEALTH_CHECK" = true ]; then
|
|
warn "Skipping health checks (--skip-health-check enabled)"
|
|
exit 0
|
|
fi
|
|
|
|
log "Waiting for services to be healthy..."
|
|
sleep 10
|
|
|
|
# Check backend health
|
|
BACKEND_HEALTH=false
|
|
for _ in {1..30}; do
|
|
if curl -sf http://localhost:4025/health/live > /dev/null 2>&1; then
|
|
BACKEND_HEALTH=true
|
|
break
|
|
fi
|
|
echo -n "."
|
|
sleep 2
|
|
done
|
|
echo ""
|
|
|
|
if [ "$BACKEND_HEALTH" = true ]; then
|
|
ok "Backend health check passed (http://localhost:4025)"
|
|
else
|
|
fail "Backend health check failed"
|
|
fi
|
|
|
|
# Check web health
|
|
WEB_HEALTH=false
|
|
for _ in {1..30}; do
|
|
if curl -sf http://localhost:3085 > /dev/null 2>&1; then
|
|
WEB_HEALTH=true
|
|
break
|
|
fi
|
|
echo -n "."
|
|
sleep 2
|
|
done
|
|
echo ""
|
|
|
|
if [ "$WEB_HEALTH" = true ]; then
|
|
ok "Web health check passed (http://localhost:3085)"
|
|
else
|
|
warn "Web health check failed (may be starting up)"
|
|
fi
|
|
|
|
# ── Endpoint Verification ─────────────────────────────────────────────
|
|
log "Verifying production endpoints..."
|
|
|
|
API_ENDPOINT="https://api.bytelyst.com/invttrdg"
|
|
WEB_ENDPOINT="https://invttrdg.bytelyst.com"
|
|
|
|
# Check API endpoint
|
|
if curl -sf "$API_ENDPOINT/health/live" > /dev/null 2>&1; then
|
|
ok "API endpoint accessible: $API_ENDPOINT"
|
|
else
|
|
warn "API endpoint not accessible: $API_ENDPOINT (may need DNS propagation)"
|
|
fi
|
|
|
|
# Check web endpoint
|
|
if curl -sf "$WEB_ENDPOINT" > /dev/null 2>&1; then
|
|
ok "Web endpoint accessible: $WEB_ENDPOINT"
|
|
else
|
|
warn "Web endpoint not accessible: $WEB_ENDPOINT (may need DNS propagation)"
|
|
fi
|
|
|
|
# ── API Smoke Tests (Post-Deployment) ───────────────────────────────────
|
|
log "Running post-deployment API smoke tests..."
|
|
|
|
# Test backend health endpoint
|
|
BACKEND_URL="http://localhost:4025"
|
|
if curl -sf "$BACKEND_URL/health/live" > /dev/null 2>&1; then
|
|
ok "Backend health endpoint responding"
|
|
# Try to get health details
|
|
HEALTH_RESPONSE=$(curl -s "$BACKEND_URL/health/live" 2>/dev/null || echo "{}")
|
|
log "Health response: $HEALTH_RESPONSE"
|
|
else
|
|
fail "Backend health endpoint not responding"
|
|
fi
|
|
|
|
# Test web is serving content
|
|
WEB_URL="http://localhost:3085"
|
|
if curl -sf "$WEB_URL" > /dev/null 2>&1; then
|
|
ok "Web frontend is serving content"
|
|
# Check if it's actually HTML (nginx serving the SPA)
|
|
CONTENT_TYPE=$(curl -sI "$WEB_URL" | grep -i content-type || echo "")
|
|
if echo "$CONTENT_TYPE" | grep -qi "text/html"; then
|
|
ok "Web frontend is serving HTML content"
|
|
else
|
|
warn "Web frontend content type unexpected: $CONTENT_TYPE"
|
|
fi
|
|
else
|
|
fail "Web frontend not responding"
|
|
fi
|
|
|
|
log "══════════════════════════════════════════════════════════════════════"
|
|
ok "Deployment completed successfully!"
|
|
log "Backend: http://localhost:4025 → $API_ENDPOINT"
|
|
log "Web: http://localhost:3085 → $WEB_ENDPOINT"
|
|
log "══════════════════════════════════════════════════════════════════════"
|