#!/bin/bash # ============================================================================= # DIDI Platform - Full Build From Scratch # ============================================================================= # Ridica intreaga platforma de la zero. # Ordinea: network -> data-layer -> production (redis, keycloak, kong) -> # framework (sync redis) -> agent-v3 + workers -> admin dashboard # # Cerinte: docker, docker compose v2+, conexiune la PG cluster 10.11.50.167:5000 # Rulare: chmod +x full-build.sh && ./full-build.sh [HOSTNAME] # Exemplu: ./full-build.sh # ./full-build.sh didi365.eu # Daca nu specifici hostname, il detecteaza automat din hostname -f. # ============================================================================= set -euo pipefail # -- Hostname --------------------------------------------------------------- if [ -n "${1:-}" ]; then PLATFORM_HOSTNAME="$1" else PLATFORM_HOSTNAME=$(hostname -f 2>/dev/null || hostname) fi # -- Culori ---------------------------------------------------------------- RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m' # -- Paths ----------------------------------------------------------------- BACKEND="/home/admin365/didi_mono/backend" DATA_LAYER="$BACKEND/services/data-layer" PRODUCTION="$BACKEND/production" FRAMEWORK="$BACKEND/services/orchestration-layer/didiFramework" AGENT_V3="$BACKEND/services/orchestration-layer/agent-v3" KONG_DIR="$BACKEND/services/gateway-auth-layer/didiKong" # -- Conexiune PG cluster -------------------------------------------------- PG_HOST="10.11.50.167" PG_PORT="5000" PG_USER="bos_interface" PG_PASS="interface" PG_DB="DIDI" # -- Functii helper --------------------------------------------------------- log() { echo -e "${CYAN}[$(date +%H:%M:%S)]${NC} $*"; } ok() { echo -e "${GREEN} OK${NC} $*"; } warn() { echo -e "${YELLOW} WARN${NC} $*"; } fail() { echo -e "${RED} FAIL${NC} $*"; exit 1; } wait_healthy() { local container="$1" local max_wait="${2:-120}" local elapsed=0 log "Astept container $container sa fie healthy (max ${max_wait}s)..." while [ $elapsed -lt $max_wait ]; do local status status=$(docker inspect --format='{{.State.Health.Status}}' "$container" 2>/dev/null || echo "missing") if [ "$status" = "healthy" ]; then ok "$container este healthy" return 0 fi sleep 3 elapsed=$((elapsed + 3)) done warn "$container nu a devenit healthy in ${max_wait}s (status: $status)" return 1 } pg_query() { # Executa query PG prin orice container care are psql/node disponibil local query="$1" if docker ps --format '{{.Names}}' | grep -q staging-dataLayer-postgres; then docker exec -e PGPASSWORD="$PG_PASS" staging-dataLayer-postgres \ psql -h "$PG_HOST" -p "$PG_PORT" -U "$PG_USER" -d "$PG_DB" -tAc "$query" 2>/dev/null elif docker ps --format '{{.Names}}' | grep -q didi-framework; then docker exec didi-framework node -e " const {Pool}=require('pg'); const p=new Pool({host:'$PG_HOST',port:$PG_PORT,database:'$PG_DB',user:'$PG_USER',password:'$PG_PASS'}); p.query(\`$query\`).then(r=>{r.rows.forEach(row=>console.log(Object.values(row).join('|')));p.end()}).catch(e=>{console.error(e.message);p.end();process.exit(1)}); " 2>/dev/null else return 1 fi } section() { echo "" echo -e "${YELLOW}====================================================================${NC}" echo -e "${YELLOW} $*${NC}" echo -e "${YELLOW}====================================================================${NC}" } # ============================================================================= section "FAZA 0: Verificari preliminare" # ============================================================================= log "Verific docker..." docker info >/dev/null 2>&1 || fail "Docker nu ruleaza" ok "Docker activ" log "Verific docker compose..." docker compose version >/dev/null 2>&1 || fail "Docker Compose v2 nu este instalat" ok "Docker Compose disponibil" # ============================================================================= section "FAZA 1: Docker Network" # ============================================================================= log "Creez reteaua didi-network (daca nu exista)..." docker network create didi-network 2>/dev/null && ok "Retea creata" || ok "Reteaua exista deja" # ============================================================================= section "FAZA 2: Data Layer (PostgreSQL local, RabbitMQ, MinIO, PgAdmin)" # ============================================================================= log "Verific daca volumele externe exista..." for vol in didi-staging-postgres-data didi-staging-minio-data didi-staging-pgadmin-data; do docker volume inspect "$vol" >/dev/null 2>&1 && ok "Volum $vol exista" || { log "Creez volum $vol..." docker volume create "$vol" ok "Volum $vol creat" } done log "Build + start data-layer..." cd "$DATA_LAYER" # Nota: containerul PG local este doar pentru waitlist. # Baza de date principala (DIDI) este pe clusterul extern 10.11.50.167:5000. # Dockerfile-ul custom necesita init.sql + health-check.sh care nu sunt in git (*.sql in .gitignore). # Daca Dockerfile exista SI init.sql e fisier (nu director gol), build custom; altfel, skip. if [ -f didiDatabase/Dockerfile ] && [ -f didiDatabase/init.sql ]; then log "Build imagine didi-staging-postgres..." docker build -t didi-staging-postgres:latest didiDatabase/ 2>&1 | tail -3 ok "Imagine postgres construita" else log "Skip build custom PG (init.sql lipseste). Se foloseste imaginea standard postgres:15-alpine." fi docker compose up -d --build 2>&1 | tail -5 ok "Data layer pornit" # Astept serviciile critice wait_healthy staging-dataLayer-rabbitmq 90 wait_healthy staging-dataLayer-minio 60 # ============================================================================= section "FAZA 3: Verificare PostgreSQL Cluster extern" # ============================================================================= log "Testez conexiunea la PG cluster $PG_HOST:$PG_PORT..." # Astept sa avem un container cu psql sau node sleep 5 SCHEMA_COUNT=$(pg_query "SELECT count(*) FROM information_schema.schemata WHERE schema_name IN ('bos_analysis','bos_parammgmt','bos_sysadmin','bos_subscriber')" 2>/dev/null || echo "0") if [ "$SCHEMA_COUNT" = "4" ]; then ok "Toate 4 schemele exista in PG cluster — nu ating nimic" elif [ "$SCHEMA_COUNT" = "0" ]; then warn "ZERO scheme bos_* gasite — baza DIDI este goala" # Caut exportul complet DIDI_EXPORT="" for candidate in \ "$BACKEND/services/data-layer/didiDatabase/DIDI_full_export_2026-07-02.sql" \ "$BACKEND/services/data-layer/didiDatabase"/DIDI_full_export_*.sql; do if [ -f "$candidate" ]; then DIDI_EXPORT="$candidate" break fi done if [ -n "$DIDI_EXPORT" ]; then EXPORT_SIZE=$(du -h "$DIDI_EXPORT" | cut -f1) log "Gasit export: $DIDI_EXPORT ($EXPORT_SIZE)" log "Baza este GOALA (0 scheme). Import exportul complet..." # Astept container-ul postgres local sa fie up (are psql) wait_healthy staging-dataLayer-postgres 60 || true # Copiez fisierul in container si import prin psql docker cp "$DIDI_EXPORT" staging-dataLayer-postgres:/tmp/didi_import.sql docker exec -e PGPASSWORD="$PG_PASS" staging-dataLayer-postgres \ psql -h "$PG_HOST" -p "$PG_PORT" -U "$PG_USER" -d "$PG_DB" \ -f /tmp/didi_import.sql 2>&1 | tail -20 # Verificare post-import SCHEMA_COUNT_POST=$(pg_query "SELECT count(*) FROM information_schema.schemata WHERE schema_name IN ('bos_analysis','bos_parammgmt','bos_sysadmin','bos_subscriber')" 2>/dev/null || echo "0") if [ "$SCHEMA_COUNT_POST" = "4" ]; then ok "Import reusit — toate 4 schemele exista acum" else fail "Import esuat — doar $SCHEMA_COUNT_POST/4 scheme dupa import" fi # Cleanup docker exec staging-dataLayer-postgres rm -f /tmp/didi_import.sql else warn "Nu gasesc fisier DIDI_full_export_*.sql in $BACKEND/services/data-layer/didiDatabase/" warn "Baza DIDI este goala si nu pot importa automat." read -p "Continui fara baza de date? (y/N): " answer [ "$answer" = "y" ] || [ "$answer" = "Y" ] || exit 1 fi else warn "Gasit $SCHEMA_COUNT/4 scheme (partial). Nu ating — nu e gol, dar nici complet." pg_query "SELECT schema_name FROM information_schema.schemata WHERE schema_name LIKE 'bos_%' ORDER BY 1" || true echo "" warn "Verifica manual ce lipseste. Importul automat ruleaza DOAR pe baza complet goala." read -p "Continui oricum? (y/N): " answer [ "$answer" = "y" ] || [ "$answer" = "Y" ] || exit 1 fi # Verific tabelele critice per schema log "Verific tabele critice..." CRITICAL_TABLES=( "bos_analysis|analysis_session" "bos_analysis|analysis_verdict" "bos_parammgmt|dimension" "bos_parammgmt|technique" "bos_parammgmt|verdict_category" "bos_parammgmt|component_weight" "bos_parammgmt|component_config" "bos_parammgmt|input_type_profile" "bos_sysadmin|internet_user" "bos_sysadmin|user_credential" "bos_sysadmin|subscription_plan" ) MISSING=0 for entry in "${CRITICAL_TABLES[@]}"; do schema="${entry%%|*}" table="${entry##*|}" EXISTS=$(pg_query "SELECT count(*) FROM information_schema.tables WHERE table_schema='$schema' AND table_name='$table'" 2>/dev/null || echo "0") if [ "$EXISTS" = "1" ]; then ok " $schema.$table" else warn " LIPSA: $schema.$table" MISSING=$((MISSING + 1)) fi done if [ "$MISSING" -gt 0 ]; then warn "$MISSING tabele critice lipsa. Migrari necesare." fi # ============================================================================= section "FAZA 4: Production Services (Redis, Keycloak, Kong)" # ============================================================================= cd "$PRODUCTION" # -- Seteaza KC_HOSTNAME_URL in .env pe baza hostname-ului platformei -------- log "Configurez Keycloak hostname: $PLATFORM_HOSTNAME" if grep -q "^KC_HOSTNAME_URL=" .env 2>/dev/null; then sed -i "s|^KC_HOSTNAME_URL=.*|KC_HOSTNAME_URL=https://${PLATFORM_HOSTNAME}/auth|" .env ok "KC_HOSTNAME_URL actualizat in .env" else echo "KC_HOSTNAME_URL=https://${PLATFORM_HOSTNAME}/auth" >> .env ok "KC_HOSTNAME_URL adaugat in .env" fi log "Start Redis + Keycloak + Kong..." docker compose up -d --build 2>&1 | tail -5 ok "Production services pornite" wait_healthy didi-cache 30 log "Verific conexiunea Redis..." REDIS_PONG=$(docker exec didi-cache redis-cli -a redis123 ping 2>/dev/null || echo "FAIL") if [ "$REDIS_PONG" = "PONG" ]; then ok "Redis raspunde" else warn "Redis nu raspunde: $REDIS_PONG" fi wait_healthy keycloak 180 wait_healthy kong 90 # -- Configurare Keycloak redirect URIs via Admin API ----------------------- section "FAZA 4b: Keycloak - Configurare redirect URIs pentru $PLATFORM_HOSTNAME" log "Obtin token admin Keycloak..." KC_ADMIN_USER=$(grep "^KEYCLOAK_ADMIN=" .env | cut -d= -f2-) KC_ADMIN_PASS=$(grep "^KEYCLOAK_ADMIN_PASSWORD=" .env | cut -d= -f2-) KC_TOKEN=$(curl -s -X POST "http://localhost:28000/realms/master/protocol/openid-connect/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "username=${KC_ADMIN_USER}" -d "password=${KC_ADMIN_PASS}" \ -d "grant_type=password" -d "client_id=admin-cli" 2>/dev/null \ | python3 -c "import sys,json;print(json.load(sys.stdin).get('access_token',''))" 2>/dev/null || echo "") if [ -z "$KC_TOKEN" ]; then warn "Nu am obtinut token admin Keycloak. Redirect URIs trebuie configurate manual." else ok "Token admin obtinut" # Configureaza didi-web-app log "Configurez client didi-web-app..." WEB_CLIENT_UUID=$(curl -s "http://localhost:28000/admin/realms/didi-clients/clients?clientId=didi-web-app" \ -H "Authorization: Bearer $KC_TOKEN" 2>/dev/null \ | python3 -c "import sys,json;d=json.load(sys.stdin);print(d[0]['id'] if d else '')" 2>/dev/null || echo "") if [ -n "$WEB_CLIENT_UUID" ]; then curl -s -X PUT "http://localhost:28000/admin/realms/didi-clients/clients/$WEB_CLIENT_UUID" \ -H "Authorization: Bearer $KC_TOKEN" \ -H "Content-Type: application/json" \ -d "{ \"redirectUris\": [ \"https://${PLATFORM_HOSTNAME}/*\", \"http://localhost:3001/*\", \"http://localhost:5173/*\" ], \"webOrigins\": [ \"https://${PLATFORM_HOSTNAME}\", \"http://localhost:3001\", \"http://localhost:5173\" ] }" -w "" -o /dev/null 2>/dev/null ok "didi-web-app: redirect URI -> https://${PLATFORM_HOSTNAME}/*" else warn "Client didi-web-app nu gasit in Keycloak" fi # Configureaza admin-dashboard log "Configurez client admin-dashboard..." ADMIN_CLIENT_UUID=$(curl -s "http://localhost:28000/admin/realms/didi-clients/clients?clientId=admin-dashboard" \ -H "Authorization: Bearer $KC_TOKEN" 2>/dev/null \ | python3 -c "import sys,json;d=json.load(sys.stdin);print(d[0]['id'] if d else '')" 2>/dev/null || echo "") if [ -n "$ADMIN_CLIENT_UUID" ]; then curl -s -X PUT "http://localhost:28000/admin/realms/didi-clients/clients/$ADMIN_CLIENT_UUID" \ -H "Authorization: Bearer $KC_TOKEN" \ -H "Content-Type: application/json" \ -d "{ \"redirectUris\": [ \"https://${PLATFORM_HOSTNAME}/*\", \"https://${PLATFORM_HOSTNAME}/admin/*\", \"http://localhost:3003/*\" ], \"webOrigins\": [ \"https://${PLATFORM_HOSTNAME}\", \"http://localhost:3003\" ] }" -w "" -o /dev/null 2>/dev/null ok "admin-dashboard: redirect URI -> https://${PLATFORM_HOSTNAME}/*" else warn "Client admin-dashboard nu gasit in Keycloak" fi fi # ============================================================================= section "FAZA 5: didiFramework (CRUD backend + sync Redis)" # ============================================================================= cd "$FRAMEWORK" log "Build + start didiFramework..." docker compose up -d --build 2>&1 | tail -5 ok "didiFramework pornit" wait_healthy didi-framework 60 # Verific health-ul complet (PG + MinIO) log "Verific health didiFramework..." HEALTH=$(docker exec didi-framework wget -qO- "http://127.0.0.1:3005/health/all" 2>/dev/null || echo "{}") echo " $HEALTH" # ============================================================================= section "FAZA 6: Sincronizare Framework -> Redis" # ============================================================================= log "Trigger sync-redis (incarca parametri framework in Redis)..." SYNC_RESULT=$(docker exec didi-framework wget -qO- --post-data='' "http://127.0.0.1:3005/api/sync-redis" 2>/dev/null || echo "FAIL") if echo "$SYNC_RESULT" | grep -q '"success"'; then ok "Sync Redis reusit" echo " $SYNC_RESULT" | head -c 200 echo "" else warn "Sync Redis posibil esuat: $SYNC_RESULT" warn "Poti face sync manual mai tarziu: POST http://localhost:3005/api/sync-redis" fi # Verific ca cheile au fost scrise log "Verific chei framework in Redis..." KEY_COUNT=$(docker exec didi-cache redis-cli -a redis123 keys "didi:framework:*" 2>/dev/null | wc -l) CONFIG_COUNT=$(docker exec didi-cache redis-cli -a redis123 keys "didi:config:*" 2>/dev/null | wc -l) ok "Chei framework: $KEY_COUNT | Chei config: $CONFIG_COUNT" if [ "$KEY_COUNT" -lt 5 ]; then warn "Prea putine chei framework ($KEY_COUNT). Sync-ul poate sa nu fi functionat." warn "Verifica manual: docker exec didi-cache redis-cli -a redis123 keys 'didi:framework:*'" fi # ============================================================================= section "FAZA 7: MinIO - Initializare bucket-uri" # ============================================================================= log "Verific bucket-urile MinIO..." BUCKET_LIST=$(docker exec staging-dataLayer-minio mc ls local/ 2>/dev/null || echo "") REQUIRED_BUCKETS=("uploads" "text-files" "image-files" "audio-files" "video-files" "document-files" "pipeline-artifacts" "backups") BUCKETS_MISSING=0 for bucket in "${REQUIRED_BUCKETS[@]}"; do if echo "$BUCKET_LIST" | grep -q "$bucket"; then ok " Bucket: $bucket" else warn " LIPSA bucket: $bucket" BUCKETS_MISSING=$((BUCKETS_MISSING + 1)) fi done if [ "$BUCKETS_MISSING" -gt 0 ]; then log "Rulez init-buckets.sh..." if [ -f "$DATA_LAYER/didiStorage/init-buckets.sh" ]; then docker exec -e MINIO_HOST=localhost -e MINIO_PORT=9000 \ -e MINIO_ROOT_USER=minioadmin -e MINIO_ROOT_PASSWORD=minio123 \ staging-dataLayer-minio sh -c "$(cat $DATA_LAYER/didiStorage/init-buckets.sh)" 2>&1 | tail -5 ok "Bucket-uri initializate" else warn "init-buckets.sh nu exista. Creeaza bucket-urile manual." fi else ok "Toate bucket-urile exista" fi # ============================================================================= section "FAZA 8: Agent V3 + Workers" # ============================================================================= cd "$AGENT_V3" # Verific ca .env exista (contine API keys) if [ ! -f .env ]; then warn ".env lipseste in $AGENT_V3" warn "Fisierul trebuie sa contina: OPENROUTER_API_KEY, OPENAI_API_KEY, GROQ_API_KEY, M17_WHISPER_TOKEN" warn "Fara aceste chei, analizele LLM nu vor functiona." read -p "Continui fara .env? (y/N): " answer [ "$answer" = "y" ] || [ "$answer" = "Y" ] || exit 1 else ok ".env exista (API keys configurate)" # Verific cheile critice for key in OPENROUTER_API_KEY OPENAI_API_KEY GROQ_API_KEY; do val=$(grep "^$key=" .env 2>/dev/null | cut -d= -f2-) if [ -z "$val" ]; then warn " $key este gol in .env" else ok " $key configurat (${#val} caractere)" fi done fi log "Build + start agent-v3 + toti workerii..." docker compose up -d --build 2>&1 | tail -10 ok "Agent V3 + workers porniti" wait_healthy didi-agent-v3 60 # Verific health log "Verific health agent-v3..." AGENT_HEALTH=$(docker exec didi-agent-v3 wget -qO- "http://localhost:24803/api/v3/health" 2>/dev/null || echo "FAIL") echo " $AGENT_HEALTH" # Verific workerii log "Verific workerii..." WORKERS=$(docker ps --format '{{.Names}}' | grep -c "agent-v3-worker" || true) AGGREGATORS=$(docker ps --format '{{.Names}}' | grep -c "verdict-aggregator" || true) ok "Workers activi: $WORKERS | Aggregators: $AGGREGATORS" # ============================================================================= section "FAZA 9: Admin Dashboard" # ============================================================================= # Admin dashboard este in data-layer docker-compose (didi-admin container) log "Verific admin dashboard..." if docker ps --format '{{.Names}}' | grep -q didi-admin; then ok "didi-admin deja ruleaza" else log "Admin dashboard nu ruleaza. Rebuild..." cd "$BACKEND/admin-dashboard" if [ -f Dockerfile ]; then docker build -t didi-admin:latest . 2>&1 | tail -5 ok "Imagine admin-dashboard construita" fi cd "$DATA_LAYER" docker compose up -d didi-admin 2>&1 | tail -3 fi wait_healthy didi-admin 60 || true # ============================================================================= section "FAZA 10: Kong Build (imagine custom)" # ============================================================================= log "Verific imaginea Kong..." if docker images didi-kong:latest --format '{{.ID}}' | head -1 | grep -q .; then ok "Imaginea didi-kong:latest exista" else log "Build imagine didi-kong..." if [ -d "$KONG_DIR" ] && [ -f "$KONG_DIR/Dockerfile" ]; then docker build -t didi-kong:latest "$KONG_DIR" 2>&1 | tail -3 ok "Imaginea Kong construita. Restart Kong..." cd "$PRODUCTION" docker compose up -d kong 2>&1 | tail -3 wait_healthy kong 90 || true else warn "Nu gasesc Dockerfile Kong la $KONG_DIR" fi fi # ============================================================================= section "VERIFICARE FINALA" # ============================================================================= echo "" log "Status toate containerele DIDI:" echo "" printf "%-45s %-20s %s\n" "CONTAINER" "STATUS" "PORTS" printf "%-45s %-20s %s\n" "---------" "------" "-----" docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" --filter "network=didi-network" 2>/dev/null | tail -n +2 | sort | while read line; do echo " $line" done echo "" log "Health checks rapide:" # Lista de verificari declare -A CHECKS=( ["Redis"]="docker exec didi-cache redis-cli -a redis123 ping 2>/dev/null" ["Framework"]="docker exec didi-framework wget -qO- http://127.0.0.1:3005/health 2>/dev/null" ["Agent-V3"]="docker exec didi-agent-v3 wget -qO- http://localhost:24803/api/v3/health 2>/dev/null" ["RabbitMQ"]="docker exec staging-dataLayer-rabbitmq rabbitmq-diagnostics -q ping 2>/dev/null" ["MinIO"]="docker exec staging-dataLayer-minio mc ready local 2>/dev/null" ) for name in Redis Framework Agent-V3 RabbitMQ MinIO; do result=$(eval "${CHECKS[$name]}" || echo "FAIL") if echo "$result" | grep -qiE "PONG|ok|healthy|ready|service|READY"; then ok "$name" else warn "$name: $result" fi done echo "" # Verific chei Redis finale FRAMEWORK_KEYS=$(docker exec didi-cache redis-cli -a redis123 keys "didi:framework:*" 2>/dev/null | wc -l) CONFIG_KEYS=$(docker exec didi-cache redis-cli -a redis123 keys "didi:config:*" 2>/dev/null | wc -l) log "Redis: $FRAMEWORK_KEYS chei framework, $CONFIG_KEYS chei config" echo "" echo -e "${GREEN}====================================================================${NC}" echo -e "${GREEN} BUILD COMPLET${NC}" echo -e "${GREEN}====================================================================${NC}" echo "" echo "Platforma configurata pe: $PLATFORM_HOSTNAME" echo "" echo "Endpoint-uri disponibile:" echo " Frontend: https://${PLATFORM_HOSTNAME}" echo " Admin Dashboard: https://${PLATFORM_HOSTNAME}/admin" echo " Agent V3 API: http://localhost:24803/api/v3/health (doar local)" echo " Framework API: intern pe Docker network (port 3005)" echo " Kong Gateway: https://localhost:443" echo " Keycloak: http://localhost:28000" echo " Keycloak Auth: https://${PLATFORM_HOSTNAME}/auth" echo " RabbitMQ UI: http://localhost:15672 (admin/rabbitmq123)" echo " MinIO Console: http://localhost:9001 (minioadmin/minio123)" echo " PgAdmin: http://localhost:5050 (admin@example.com/admin123)" echo "" echo "Daca sync Redis nu a mers, ruleaza manual:" echo " curl -X POST http://localhost:3005/api/sync-redis" echo " (sau din interiorul Docker: docker exec didi-framework wget -qO- --post-data='' http://127.0.0.1:3005/api/sync-redis)" echo ""