bytelyst-devops-tools/deploy-invttrdg.sh
root 5d55bd1720 Add interactive menu and package checks to deployment script
- Add interactive numbered menu for manual deployments (7 options)
- Add --no-cache flag support for forcing Docker rebuilds
- Add package publication verification before Docker build
- Display configuration summary before deployment
- Maintain backward compatibility with CLI arguments

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-05-12 03:39:39 +00:00

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 \
--secret id=gitea_npm_token,env=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 "══════════════════════════════════════════════════════════════════════"