Files
2026-09-19 16:35:35 +08:00

602 lines
22 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_NAME="${PROJECT_NAME:-bytedesk}"
# ============================================================
# 微语 Docker Compose 组合启动脚本(纯关键字驱动)
#
# 用法:./start.sh [关键字...]
# 关键字任意顺序、任意组合;不传参数等价于 ./start.sh all
#
# 数据库(最多选 1 个,默认 mysql):
# mysql | postgresql(别名 pg) | oracle | kingbase(别名 kingbase9)
# 消息队列(最多选 1 个,默认 artemis):
# artemis | rabbitmq
# 核心中间件(默认自动包含,显式写也不报错):
# redis | elasticsearch(别名 es)
# 组件(任意组合):
# freeswitch | mrcp | coturn | janus
# searxng(别名 search) | neo4j | logstash | kibana | minio
# prometheus | grafana | zipkin | otelcol(别名 opentelemetry) | gotenberg(文件预览转换)
# 组合关键字:
# call = freeswitch + mrcp
# webrtc = coturn + janus
# obs(别名 observability) = prometheus + grafana + zipkin + otelcol
# 目标:
# middleware = redis + elasticsearch + 所选db + 所选mq(不含 bytedesk 应用,源码本地开发)
# all / bytedesk = middleware 全部 + bytedesk 应用(默认)
#
# 示例:
# ./start.sh # 默认全栈:mysql + artemis + redis + es + bytedesk
# ./start.sh middleware # 仅中间件(源码本地开发)
# ./start.sh postgresql rabbitmq middleware # PostgreSQL + RabbitMQ 中间件
# ./start.sh call middleware # 呼叫中心中间件(freeswitch + mrcp
# ./start.sh call webrtc all obs logstash kibana
# ./start.sh all minio searxng neo4j
#
# 说明:
# - 启用 bytedesk 应用时,脚本会按所选 db/mq 自动注入 SPRING_DATASOURCE_* / MQ
# 环境变量并写入 .env.appdocker compose 通过 --env-file 加载);
# .env 或 shell 环境变量中的显式配置优先级更高。
# - mysql/postgresql/oracle/kingbase 场景下会自动确保应用数据库存在(不存在则创建)。
# ============================================================
# compose 文件统一位于 ./compose/ 目录(一镜像一文件),相对路径均以该目录为基准
COMPOSE_DIR="${SCRIPT_DIR}/compose"
DB=""
MQ=""
ENABLE_FREESWITCH=false
ENABLE_MRCP=false
ENABLE_COTURN=false
ENABLE_JANUS=false
ENABLE_SEARXNG=false
ENABLE_NEO4J=false
ENABLE_LOGSTASH=false
ENABLE_KIBANA=false
ENABLE_MINIO=false
ENABLE_PROMETHEUS=false
ENABLE_GRAFANA=false
ENABLE_ZIPKIN=false
ENABLE_OTELCOL=false
ENABLE_GOTENBERG=false
TARGET=""
usage() {
sed -n '10,44p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
exit 1
}
set_db() {
if [[ -n "${DB}" ]]; then
echo "[ERROR] Duplicate db keyword: '${DB}' and '$1'. Pick exactly one."
exit 1
fi
DB="$1"
}
set_mq() {
if [[ -n "${MQ}" ]]; then
echo "[ERROR] Duplicate mq keyword: '${MQ}' and '$1'. Pick exactly one."
exit 1
fi
MQ="$1"
}
set_target() {
if [[ -n "${TARGET}" ]]; then
echo "[ERROR] Conflicting target keywords: '${TARGET}' and '$1'."
exit 1
fi
TARGET="$1"
}
for arg in "$@"; do
case "${arg}" in
mysql) set_db mysql ;;
postgresql|pg) set_db postgresql ;;
oracle) set_db oracle ;;
kingbase|kingbase9) set_db kingbase ;;
artemis) set_mq artemis ;;
rabbitmq) set_mq rabbitmq ;;
freeswitch) ENABLE_FREESWITCH=true ;;
mrcp) ENABLE_MRCP=true ;;
coturn) ENABLE_COTURN=true ;;
janus) ENABLE_JANUS=true ;;
searxng|search) ENABLE_SEARXNG=true ;;
neo4j) ENABLE_NEO4J=true ;;
logstash) ENABLE_LOGSTASH=true ;;
kibana) ENABLE_KIBANA=true ;;
minio) ENABLE_MINIO=true ;;
prometheus) ENABLE_PROMETHEUS=true ;;
grafana) ENABLE_GRAFANA=true ;;
zipkin) ENABLE_ZIPKIN=true ;;
otelcol|opentelemetry) ENABLE_OTELCOL=true ;;
gotenberg) ENABLE_GOTENBERG=true ;;
call)
ENABLE_FREESWITCH=true
ENABLE_MRCP=true
;;
webrtc)
ENABLE_COTURN=true
ENABLE_JANUS=true
;;
obs|observability)
ENABLE_PROMETHEUS=true
ENABLE_GRAFANA=true
ENABLE_ZIPKIN=true
ENABLE_OTELCOL=true
;;
middleware) set_target middleware ;;
all|bytedesk|app) set_target all ;;
-h|--help|help) usage ;;
*)
echo "[ERROR] Unknown keyword: '${arg}'"
echo "Allowed: mysql|postgresql|pg|oracle|kingbase|kingbase9 artemis|rabbitmq redis|elasticsearch|es"
echo " freeswitch mrcp coturn janus searxng|search neo4j logstash kibana minio prometheus grafana zipkin otelcol gotenberg"
echo " call webrtc obs middleware all"
exit 1
;;
esac
done
# 默认值与归一化
DB="${DB:-mysql}"
MQ="${MQ:-artemis}"
TARGET="${TARGET:-all}"
# redis + elasticsearch 为核心中间件,任何栈都自动包含
ENABLE_REDIS=true
ENABLE_ELASTICSEARCH=true
# 组合约束校验
if [[ "${ENABLE_FREESWITCH}" == true || "${ENABLE_MRCP}" == true ]]; then
case "${DB}" in
mysql|postgresql) ;;
oracle|kingbase)
echo "[ERROR] freeswitch/mrcp (call) does not support ${DB}. Allowed db: mysql|postgresql"
exit 1
;;
esac
fi
# ============================================================
# 组装 compose 文件列表(中间件部分,不含应用)
# ============================================================
middleware_files=()
add_file() {
local file="${COMPOSE_DIR}/$1"
if [[ ! -f "${file}" ]]; then
echo "[ERROR] Missing compose file: ${file}"
exit 1
fi
middleware_files+=( -f "${file}" )
}
add_file compose-redis.yaml
add_file compose-elasticsearch.yaml
add_file "compose-${DB}.yaml"
add_file "compose-${MQ}.yaml"
[[ "${ENABLE_FREESWITCH}" == true ]] && add_file compose-freeswitch.yaml
[[ "${ENABLE_MRCP}" == true ]] && add_file compose-mrcp.yaml
[[ "${ENABLE_COTURN}" == true ]] && add_file compose-coturn.yaml
[[ "${ENABLE_JANUS}" == true ]] && add_file compose-janus.yaml
[[ "${ENABLE_SEARXNG}" == true ]] && add_file compose-searxng.yaml
[[ "${ENABLE_NEO4J}" == true ]] && add_file compose-neo4j.yaml
[[ "${ENABLE_LOGSTASH}" == true ]] && add_file compose-logstash.yaml
[[ "${ENABLE_KIBANA}" == true ]] && add_file compose-kibana.yaml
[[ "${ENABLE_MINIO}" == true ]] && add_file compose-minio.yaml
[[ "${ENABLE_PROMETHEUS}" == true ]] && add_file compose-prometheus.yaml
[[ "${ENABLE_GRAFANA}" == true ]] && add_file compose-grafana.yaml
[[ "${ENABLE_ZIPKIN}" == true ]] && add_file compose-zipkin.yaml
[[ "${ENABLE_OTELCOL}" == true ]] && add_file compose-otelcol.yaml
[[ "${ENABLE_GOTENBERG}" == true ]] && add_file compose-gotenberg.yaml
APP_FILE="${COMPOSE_DIR}/compose-bytedesk.yaml"
if [[ "${TARGET}" == "all" && ! -f "${APP_FILE}" ]]; then
echo "[ERROR] Missing compose file: ${APP_FILE}"
exit 1
fi
ENV_FILE_ARGS=()
if [[ -f "${SCRIPT_DIR}/.env" ]]; then
ENV_FILE_ARGS+=( --env-file "${SCRIPT_DIR}/.env" )
fi
APP_ENV_FILE="${SCRIPT_DIR}/.env.app"
if [[ -f "${APP_ENV_FILE}" ]]; then
ENV_FILE_ARGS+=( --env-file "${APP_ENV_FILE}" )
fi
# ============================================================
# .env 读取辅助
# ============================================================
get_env_value() {
local key="$1"
local default_value="$2"
local env_file="${SCRIPT_DIR}/.env"
if [[ ! -f "${env_file}" ]]; then
echo "${default_value}"
return
fi
local line
line="$(grep -E "^[[:space:]]*${key}=" "${env_file}" | tail -n 1 || true)"
if [[ -z "${line}" ]]; then
echo "${default_value}"
return
fi
local value
value="${line#*=}"
value="${value%$'\r'}"
value="${value%\"}"
value="${value#\"}"
value="${value%\'}"
value="${value#\'}"
if [[ -z "${value}" ]]; then
echo "${default_value}"
else
echo "${value}"
fi
}
# key 是否在 .env 中显式定义(存在 key= 行即算,无论值是否为空)
env_key_defined() {
local key="$1"
[[ -f "${SCRIPT_DIR}/.env" ]] || return 1
grep -Eq "^[[:space:]]*${key}=" "${SCRIPT_DIR}/.env"
}
# ============================================================
# 应用环境注入:优先级 shell 环境变量 > .env 显式配置 > 脚本注入
# 注入结果写入 .env.app,供 docker compose 与 watchdog 后续使用
# ============================================================
app_env_lines=()
inject_env() {
local key="$1"
local value="$2"
if [[ -n "${!key:-}" ]]; then
# 已在 shell 环境中显式设置:记录到 .env.app,保证 watchdog/后续 compose 可复现
app_env_lines+=( "${key}=${!key}" )
return
fi
if env_key_defined "${key}"; then
# .env 中显式配置:docker compose 通过 --env-file 直接读取,无需注入
return
fi
export "${key}=${value}"
app_env_lines+=( "${key}=${value}" )
}
inject_app_env() {
local mysql_port mysql_database mysql_root_user
local pg_port pg_db pg_user
local kb_port kb_database kb_user
mysql_port="$(get_env_value MYSQL_PORT 3306)"
mysql_database="$(get_env_value MYSQL_DATABASE bytedesk)"
mysql_root_user="$(get_env_value MYSQL_ROOT_USER root)"
pg_port="$(get_env_value POSTGRES_PORT 5432)"
pg_db="$(get_env_value POSTGRES_DB bytedesk)"
pg_user="$(get_env_value POSTGRES_USER postgres)"
kb_port="$(get_env_value KINGBASE_PORT 54321)"
kb_database="$(get_env_value KINGBASE_DATABASE bytedesk)"
kb_user="$(get_env_value KINGBASE_DB_USER root)"
case "${DB}" in
mysql)
inject_env SPRING_DATASOURCE_URL "jdbc:mysql://bytedesk-mysql:${mysql_port}/${mysql_database}?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true"
inject_env SPRING_DATASOURCE_USERNAME "${mysql_root_user}"
inject_env SPRING_DATASOURCE_PASSWORD "$(get_env_value MYSQL_ROOT_PASSWORD '')"
;;
postgresql)
inject_env SPRING_DATASOURCE_URL "jdbc:postgresql://bytedesk-postgresql:${pg_port}/${pg_db}"
inject_env SPRING_DATASOURCE_USERNAME "${pg_user}"
inject_env SPRING_DATASOURCE_PASSWORD "$(get_env_value POSTGRES_PASSWORD '')"
inject_env SPRING_DATASOURCE_DRIVER_CLASS_NAME "org.postgresql.Driver"
;;
oracle)
inject_env SPRING_DATASOURCE_URL "jdbc:oracle:thin:@bytedesk-oracle:1521/FREEPDB1"
inject_env SPRING_DATASOURCE_USERNAME "$(get_env_value ORACLE_APP_USER bytedesk)"
inject_env SPRING_DATASOURCE_PASSWORD "$(get_env_value ORACLE_APP_USER_PASSWORD '')"
inject_env SPRING_DATASOURCE_DRIVER_CLASS_NAME "oracle.jdbc.OracleDriver"
;;
kingbase)
inject_env SPRING_DATASOURCE_URL "jdbc:kingbase8://bytedesk-kingbase:${kb_port}/${kb_database}?createDatabaseIfNotExist=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&options=-c%20default_tablespace=sys_default"
inject_env SPRING_DATASOURCE_USERNAME "${kb_user}"
inject_env SPRING_DATASOURCE_PASSWORD "$(get_env_value KINGBASE_DB_PASSWORD '')"
inject_env SPRING_DATASOURCE_DRIVER_CLASS_NAME "com.kingbase8.Driver"
inject_env SPRING_JPA_PROPERTIES_HIBERNATE_DIALECT "org.hibernate.dialect.PostgreSQLDialect"
inject_env SPRING_QUARTZ_JDBC_PLATFORM "postgres"
inject_env SPRING_BATCH_JDBC_PLATFORM "postgres"
inject_env SPRING_BATCH_DATABASE_TYPE "POSTGRES"
inject_env FLOWABLE_DATABASE_TYPE "postgres"
inject_env SPRING_LIQUIBASE_ENABLED "false"
;;
esac
case "${MQ}" in
artemis)
inject_env BYTEDESK_MQ_TYPE "artemis"
inject_env BYTEDESK_MQ_RABBITMQ_ENABLED "false"
inject_env SPRING_ARTEMIS_MODE "embedded"
inject_env SPRING_ARTEMIS_BROKER_URL "tcp://bytedesk-artemis:61616"
inject_env SPRING_ARTEMIS_USER "$(get_env_value ARTEMIS_USER admin)"
inject_env SPRING_ARTEMIS_PASSWORD "$(get_env_value ARTEMIS_PASSWORD '')"
inject_env SPRING_JMS_CACHE_ENABLED "true"
;;
rabbitmq)
inject_env BYTEDESK_MQ_TYPE "rabbitmq"
inject_env BYTEDESK_MQ_RABBITMQ_ENABLED "true"
inject_env SPRING_RABBITMQ_HOST "bytedesk-rabbitmq"
inject_env SPRING_RABBITMQ_PORT "5672"
inject_env SPRING_RABBITMQ_USERNAME "$(get_env_value RABBITMQ_DEFAULT_USER admin)"
inject_env SPRING_RABBITMQ_PASSWORD "$(get_env_value RABBITMQ_DEFAULT_PASS '')"
inject_env SPRING_RABBITMQ_VIRTUAL_HOST "/"
inject_env SPRING_RABBITMQ_CONNECTION_TIMEOUT "5000"
inject_env SPRING_ARTEMIS_MODE "embedded"
;;
esac
}
write_app_env_file() {
if [[ ${#app_env_lines[@]} -eq 0 ]]; then
return
fi
{
echo "# Auto-generated by start.sh at $(date '+%Y-%m-%d %H:%M:%S') — do not edit."
echo "# bytedesk 应用数据源/MQ 连接注入(db=${DB}, mq=${MQ});显式配置请使用 .env 或环境变量"
printf '%s\n' "${app_env_lines[@]}"
} > "${APP_ENV_FILE}"
}
# ============================================================
# 数据库自动建库(exec 目标服务:bytedesk-<db>
# ============================================================
ensure_kingbase_database() {
local db_user db_password db_name
db_user="$(get_env_value "KINGBASE_DB_USER" "root")"
db_password="$(get_env_value "KINGBASE_DB_PASSWORD" "")"
db_name="$(get_env_value "KINGBASE_DATABASE" "bytedesk")"
if [[ -z "${db_password}" ]]; then
echo "[WARN] KINGBASE_DB_PASSWORD is empty, skip auto database ensure for kingbase"
return
fi
local exists_check="SELECT COUNT(1) FROM sys_database WHERE datname='${db_name}';"
local create_sql="CREATE DATABASE ${db_name} WITH ENCODING 'UTF8' TEMPLATE template1 OWNER ${db_user};"
local tries=30
local count_output=""
for ((i=1; i<=tries; i++)); do
count_output="$(docker compose \
"${ENV_FILE_ARGS[@]}" \
-p "${PROJECT_NAME}" \
"${middleware_files[@]}" \
exec -T bytedesk-kingbase sh -lc "printf '%s\\n' '${db_password}' | /home/kingbase/install/kingbase/bin/ksql -h 127.0.0.1 -p ${KINGBASE_PORT:-54321} -U ${db_user} -d kingbase -W -t -A -c \"${exists_check}\"" 2>/dev/null || true)"
count_output="$(echo "${count_output}" | tr -d '[:space:]')"
if [[ "${count_output}" == "1" ]]; then
return
fi
if [[ "${count_output}" == "0" ]]; then
if docker compose \
"${ENV_FILE_ARGS[@]}" \
-p "${PROJECT_NAME}" \
"${middleware_files[@]}" \
exec -T bytedesk-kingbase sh -lc "printf '%s\\n' '${db_password}' | /home/kingbase/install/kingbase/bin/ksql -h 127.0.0.1 -p ${KINGBASE_PORT:-54321} -U ${db_user} -d kingbase -W -c \"${create_sql}\"" >/dev/null 2>&1; then
echo "[INFO] Kingbase database '${db_name}' created"
return
fi
fi
sleep 2
done
echo "[WARN] Failed to auto-ensure Kingbase database '${db_name}' after ${tries} retries"
}
ensure_mysql_database() {
local db_user db_password db_name
db_user="$(get_env_value "MYSQL_ROOT_USER" "root")"
db_password="$(get_env_value "MYSQL_ROOT_PASSWORD" "")"
db_name="$(get_env_value "MYSQL_DATABASE" "bytedesk")"
if [[ -z "${db_password}" ]]; then
echo "[WARN] MYSQL_ROOT_PASSWORD is empty, skip auto database ensure for mysql"
return
fi
local check_sql="SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME='${db_name}' LIMIT 1;"
local create_sql="CREATE DATABASE IF NOT EXISTS ${db_name} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
local tries=30
local query_output=""
for ((i=1; i<=tries; i++)); do
query_output="$(docker compose \
"${ENV_FILE_ARGS[@]}" \
-p "${PROJECT_NAME}" \
"${middleware_files[@]}" \
exec -T bytedesk-mysql sh -lc "mysql -h 127.0.0.1 -P ${MYSQL_PORT:-3306} -u${db_user} -p'${db_password}' -Nse \"${check_sql}\"" 2>/dev/null || true)"
query_output="$(echo "${query_output}" | tr -d '[:space:]')"
if [[ "${query_output}" == "${db_name}" ]]; then
return
fi
if docker compose \
"${ENV_FILE_ARGS[@]}" \
-p "${PROJECT_NAME}" \
"${middleware_files[@]}" \
exec -T bytedesk-mysql sh -lc "mysql -h 127.0.0.1 -P ${MYSQL_PORT:-3306} -u${db_user} -p'${db_password}' -Nse \"${create_sql}\"" >/dev/null 2>&1; then
echo "[INFO] MySQL database '${db_name}' created"
return
fi
sleep 2
done
echo "[WARN] Failed to auto-ensure MySQL database '${db_name}' after ${tries} retries"
}
ensure_postgresql_database() {
local db_user db_password db_name
db_user="$(get_env_value "POSTGRES_USER" "postgres")"
db_password="$(get_env_value "POSTGRES_PASSWORD" "")"
db_name="$(get_env_value "POSTGRES_DB" "bytedesk")"
if [[ -z "${db_password}" ]]; then
echo "[WARN] POSTGRES_PASSWORD is empty, skip auto database ensure for postgresql"
return
fi
local check_sql="SELECT 1 FROM pg_database WHERE datname='${db_name}';"
local create_sql="CREATE DATABASE \"${db_name}\";"
local tries=30
local query_output=""
for ((i=1; i<=tries; i++)); do
query_output="$(docker compose \
"${ENV_FILE_ARGS[@]}" \
-p "${PROJECT_NAME}" \
"${middleware_files[@]}" \
exec -T bytedesk-postgresql sh -lc "PGPASSWORD='${db_password}' psql -h 127.0.0.1 -p ${POSTGRES_PORT:-5432} -U ${db_user} -d postgres -t -A -c \"${check_sql}\"" 2>/dev/null || true)"
query_output="$(echo "${query_output}" | tr -d '[:space:]')"
if [[ "${query_output}" == "1" ]]; then
return
fi
if docker compose \
"${ENV_FILE_ARGS[@]}" \
-p "${PROJECT_NAME}" \
"${middleware_files[@]}" \
exec -T bytedesk-postgresql sh -lc "PGPASSWORD='${db_password}' psql -h 127.0.0.1 -p ${POSTGRES_PORT:-5432} -U ${db_user} -d postgres -v ON_ERROR_STOP=1 -c \"${create_sql}\"" >/dev/null 2>&1; then
echo "[INFO] PostgreSQL database '${db_name}' created"
return
fi
sleep 2
done
echo "[WARN] Failed to auto-ensure PostgreSQL database '${db_name}' after ${tries} retries"
}
ensure_oracle_database() {
local sys_password db_name app_user app_user_password
sys_password="$(get_env_value "ORACLE_PASSWORD" "")"
db_name="$(get_env_value "ORACLE_DATABASE" "bytedesk")"
app_user="$(get_env_value "ORACLE_APP_USER" "bytedesk")"
app_user_password="$(get_env_value "ORACLE_APP_USER_PASSWORD" "")"
if [[ -z "${sys_password}" ]]; then
echo "[WARN] ORACLE_PASSWORD is empty, skip auto database ensure for oracle"
return
fi
if [[ -z "${app_user_password}" ]]; then
echo "[WARN] ORACLE_APP_USER_PASSWORD is empty, skip auto database ensure for oracle"
return
fi
local db_name_upper
db_name_upper="$(echo "${db_name}" | tr '[:lower:]' '[:upper:]')"
local app_user_upper
app_user_upper="$(echo "${app_user}" | tr '[:lower:]' '[:upper:]')"
local tries=30
local query_output=""
for ((i=1; i<=tries; i++)); do
query_output="$(docker compose \
"${ENV_FILE_ARGS[@]}" \
-p "${PROJECT_NAME}" \
"${middleware_files[@]}" \
exec -T bytedesk-oracle sh -lc "printf \"SET HEADING OFF FEEDBACK OFF VERIFY OFF ECHO OFF PAGES 0\\nSELECT COUNT(1) FROM v\\$pdbs WHERE name='${db_name_upper}';\\nEXIT;\\n\" | sqlplus -s sys/'${sys_password}'@//127.0.0.1:1521/FREE as sysdba" 2>/dev/null || true)"
query_output="$(echo "${query_output}" | tr -d '[:space:]')"
if [[ "${query_output}" == "1" ]]; then
return
fi
if docker compose \
"${ENV_FILE_ARGS[@]}" \
-p "${PROJECT_NAME}" \
"${middleware_files[@]}" \
exec -T bytedesk-oracle sh -lc "printf \"WHENEVER SQLERROR EXIT 1\\nCREATE PLUGGABLE DATABASE ${db_name_upper} ADMIN USER ${app_user_upper} IDENTIFIED BY \"${app_user_password}\" FILE_NAME_CONVERT=('FREEPDB1','${db_name_upper}');\\nALTER PLUGGABLE DATABASE ${db_name_upper} OPEN;\\nEXIT;\\n\" | sqlplus -s sys/'${sys_password}'@//127.0.0.1:1521/FREE as sysdba" >/dev/null 2>&1; then
echo "[INFO] Oracle PDB '${db_name_upper}' created"
return
fi
sleep 2
done
echo "[WARN] Failed to auto-ensure Oracle PDB '${db_name_upper}' after ${tries} retries"
}
# ============================================================
# 启动
# ============================================================
components_summary=""
[[ "${ENABLE_FREESWITCH}" == true ]] && components_summary="${components_summary} freeswitch"
[[ "${ENABLE_MRCP}" == true ]] && components_summary="${components_summary} mrcp"
[[ "${ENABLE_COTURN}" == true ]] && components_summary="${components_summary} coturn"
[[ "${ENABLE_JANUS}" == true ]] && components_summary="${components_summary} janus"
[[ "${ENABLE_SEARXNG}" == true ]] && components_summary="${components_summary} searxng"
[[ "${ENABLE_LOGSTASH}" == true ]] && components_summary="${components_summary} logstash"
[[ "${ENABLE_KIBANA}" == true ]] && components_summary="${components_summary} kibana"
[[ "${ENABLE_MINIO}" == true ]] && components_summary="${components_summary} minio"
[[ "${ENABLE_PROMETHEUS}" == true ]] && components_summary="${components_summary} prometheus"
[[ "${ENABLE_GRAFANA}" == true ]] && components_summary="${components_summary} grafana"
[[ "${ENABLE_ZIPKIN}" == true ]] && components_summary="${components_summary} zipkin"
[[ "${ENABLE_OTELCOL}" == true ]] && components_summary="${components_summary} otelcol"
[[ "${ENABLE_GOTENBERG}" == true ]] && components_summary="${components_summary} gotenberg"
echo "[INFO] Starting stack: db=${DB}, mq=${MQ}, target=${TARGET}, project=${PROJECT_NAME},${components_summary:- no extra components}"
# 1) 先启动中间件(redis/es/db/mq + 所选组件),保证应用启动顺序
docker compose \
"${ENV_FILE_ARGS[@]}" \
-p "${PROJECT_NAME}" \
"${middleware_files[@]}" \
up -d
# 2) 再启动 bytedesk 应用(注入数据源/MQ 环境并写入 .env.app
if [[ "${TARGET}" == "all" ]]; then
inject_app_env
write_app_env_file
docker compose \
"${ENV_FILE_ARGS[@]}" \
-p "${PROJECT_NAME}" \
-f "${APP_FILE}" \
up -d
fi
# 3) 自动建库
case "${DB}" in
mysql)
ensure_mysql_database
;;
postgresql)
ensure_postgresql_database
;;
oracle)
ensure_oracle_database
;;
kingbase)
ensure_kingbase_database
;;
esac
echo "[INFO] Done."