didi-lot2-backend/backend/production/full-build.sh
2026-07-10 03:39:53 -07:00

574 lines
23 KiB
Bash

#!/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 <hostname>
# ./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 ""