602 lines
22 KiB
Bash
Executable File
602 lines
22 KiB
Bash
Executable File
#!/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.app(docker 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."
|