253 lines
6.9 KiB
Bash
Executable File
253 lines
6.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# 客服云 — 腾讯云 / 任意 Linux 一键生产部署
|
||
# 用法:
|
||
# ./deploy/scripts/deploy.sh # 构建并启动
|
||
# ./deploy/scripts/deploy.sh --seed # 启动后导入演示账号数据
|
||
# ./deploy/scripts/deploy.sh --pull-only # 仅 pull/up,不强制 rebuild(需已有镜像)
|
||
# ./deploy/scripts/deploy.sh --down # 停止并移除容器(保留数据卷)
|
||
# ./deploy/scripts/deploy.sh --status # 查看状态
|
||
|
||
set -euo pipefail
|
||
|
||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||
cd "$ROOT_DIR"
|
||
|
||
COMPOSE_FILE="docker-compose.prod.yml"
|
||
ENV_FILE="deploy/.env.prod"
|
||
ENV_EXAMPLE="deploy/.env.prod.example"
|
||
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
NC='\033[0m'
|
||
|
||
log() { echo -e "${GREEN}[deploy]${NC} $*"; }
|
||
warn() { echo -e "${YELLOW}[warn]${NC} $*"; }
|
||
err() { echo -e "${RED}[error]${NC} $*" >&2; }
|
||
|
||
need_cmd() {
|
||
if ! command -v "$1" >/dev/null 2>&1; then
|
||
err "缺少命令: $1"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
compose() {
|
||
docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" "$@"
|
||
}
|
||
|
||
rand_hex() {
|
||
if command -v openssl >/dev/null 2>&1; then
|
||
openssl rand -hex 24
|
||
else
|
||
head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n' | head -c 48
|
||
fi
|
||
}
|
||
|
||
ensure_env() {
|
||
if [[ ! -f "$ENV_FILE" ]]; then
|
||
if [[ ! -f "$ENV_EXAMPLE" ]]; then
|
||
err "找不到 $ENV_EXAMPLE"
|
||
exit 1
|
||
fi
|
||
log "未找到 $ENV_FILE,从示例生成…"
|
||
cp "$ENV_EXAMPLE" "$ENV_FILE"
|
||
|
||
local jwt dbpass minio_pass
|
||
jwt="$(rand_hex)"
|
||
dbpass="$(rand_hex)"
|
||
minio_pass="K$(rand_hex | head -c 16)"
|
||
|
||
# macOS / GNU sed 兼容
|
||
if sed --version >/dev/null 2>&1; then
|
||
sed -i "s|^JWT_SECRET=.*|JWT_SECRET=${jwt}|" "$ENV_FILE"
|
||
sed -i "s|^DB_PASSWORD=.*|DB_PASSWORD=${dbpass}|" "$ENV_FILE"
|
||
sed -i "s|^STORAGE_SECRET_KEY=.*|STORAGE_SECRET_KEY=${minio_pass}|" "$ENV_FILE"
|
||
else
|
||
sed -i '' "s|^JWT_SECRET=.*|JWT_SECRET=${jwt}|" "$ENV_FILE"
|
||
sed -i '' "s|^DB_PASSWORD=.*|DB_PASSWORD=${dbpass}|" "$ENV_FILE"
|
||
sed -i '' "s|^STORAGE_SECRET_KEY=.*|STORAGE_SECRET_KEY=${minio_pass}|" "$ENV_FILE"
|
||
fi
|
||
|
||
warn "已生成随机 JWT_SECRET / DB_PASSWORD / STORAGE_SECRET_KEY"
|
||
warn "请编辑 $ENV_FILE,至少设置:"
|
||
warn " APP_BASE_URL=http://你的公网IP或域名"
|
||
warn " STORAGE_PUBLIC_BASE_URL=http://你的公网IP或域名/files"
|
||
echo
|
||
read -r -p "设置完成后按回车继续(或 Ctrl+C 退出先改配置): " _
|
||
fi
|
||
|
||
# shellcheck disable=SC1090
|
||
set -a
|
||
# 去除 Windows CRLF
|
||
source <(sed 's/\r$//' "$ENV_FILE" | grep -v '^\s*#' | grep -v '^\s*$' || true)
|
||
set +a
|
||
|
||
if [[ -z "${JWT_SECRET:-}" || "${JWT_SECRET}" == *"请改"* ]]; then
|
||
err "请在 $ENV_FILE 中设置有效的 JWT_SECRET"
|
||
exit 1
|
||
fi
|
||
if [[ -z "${DB_PASSWORD:-}" || "${DB_PASSWORD}" == *"请改"* ]]; then
|
||
err "请在 $ENV_FILE 中设置有效的 DB_PASSWORD"
|
||
exit 1
|
||
fi
|
||
if [[ -z "${STORAGE_PUBLIC_BASE_URL:-}" || "${STORAGE_PUBLIC_BASE_URL}" == *"你的"* ]]; then
|
||
err "请在 $ENV_FILE 中设置 STORAGE_PUBLIC_BASE_URL(如 http://1.2.3.4/files)"
|
||
exit 1
|
||
fi
|
||
if [[ -z "${STORAGE_ACCESS_KEY:-}" || -z "${STORAGE_SECRET_KEY:-}" ]]; then
|
||
err "请设置 STORAGE_ACCESS_KEY / STORAGE_SECRET_KEY"
|
||
exit 1
|
||
fi
|
||
if [[ ${#STORAGE_SECRET_KEY} -lt 8 ]]; then
|
||
err "STORAGE_SECRET_KEY 长度至少 8 位(MinIO 要求)"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
wait_healthy() {
|
||
local url="${1:-http://127.0.0.1:${HTTP_PORT:-80}/health}"
|
||
local i
|
||
log "等待服务就绪: $url"
|
||
for i in $(seq 1 60); do
|
||
if curl -fsS "$url" >/dev/null 2>&1; then
|
||
log "健康检查通过"
|
||
return 0
|
||
fi
|
||
sleep 2
|
||
done
|
||
err "健康检查超时,请执行: docker compose -f $COMPOSE_FILE --env-file $ENV_FILE logs"
|
||
return 1
|
||
}
|
||
|
||
print_summary() {
|
||
local base="${APP_BASE_URL:-http://127.0.0.1:${HTTP_PORT:-80}}"
|
||
echo
|
||
log "========== 部署完成 =========="
|
||
echo " 访问地址: ${base}"
|
||
echo " 登录页: ${base}/login"
|
||
echo " 健康检查: ${base}/health"
|
||
echo " Widget 预览: ${base}/widget/preview"
|
||
echo " 图片前缀: ${STORAGE_PUBLIC_BASE_URL}"
|
||
echo
|
||
echo " 常用命令:"
|
||
echo " 查看状态 ./deploy/scripts/deploy.sh --status"
|
||
echo " 查看日志 docker compose -f $COMPOSE_FILE --env-file $ENV_FILE logs -f"
|
||
echo " 导入种子 ./deploy/scripts/deploy.sh --seed-only"
|
||
echo " 停止服务 ./deploy/scripts/deploy.sh --down"
|
||
echo
|
||
echo " 安全提示: 安全组仅放行 80/443;改默认密码;生产务必使用 HTTPS。"
|
||
echo "=============================="
|
||
}
|
||
|
||
cmd_up() {
|
||
local seed=0 rebuild=1
|
||
while [[ $# -gt 0 ]]; do
|
||
case "$1" in
|
||
--seed) seed=1 ;;
|
||
--pull-only) rebuild=0 ;;
|
||
*) ;;
|
||
esac
|
||
shift
|
||
done
|
||
|
||
need_cmd docker
|
||
need_cmd curl
|
||
if ! docker compose version >/dev/null 2>&1; then
|
||
err "需要 Docker Compose V2(docker compose)"
|
||
exit 1
|
||
fi
|
||
|
||
ensure_env
|
||
|
||
log "拉取基础镜像…"
|
||
compose pull postgres minio minio-init || true
|
||
|
||
if [[ "$rebuild" -eq 1 ]]; then
|
||
log "构建并启动全部服务(api + web + db + minio)…"
|
||
compose up -d --build --remove-orphans
|
||
else
|
||
log "启动服务(不强制 rebuild)…"
|
||
compose up -d --remove-orphans
|
||
fi
|
||
|
||
wait_healthy "http://127.0.0.1:${HTTP_PORT:-80}/health" || true
|
||
wait_healthy "http://127.0.0.1:${HTTP_PORT:-80}/healthz" || true
|
||
|
||
if [[ "$seed" -eq 1 ]]; then
|
||
log "导入演示种子数据…"
|
||
compose --profile seed run --rm seed || warn "种子导入失败(可能已存在数据)"
|
||
fi
|
||
|
||
compose ps
|
||
print_summary
|
||
}
|
||
|
||
cmd_seed_only() {
|
||
need_cmd docker
|
||
ensure_env
|
||
log "运行 seed…"
|
||
compose --profile seed run --rm seed
|
||
log "种子完成。默认租户管理员 kefu_admin / kefu_admin123 (若 seed 成功)"
|
||
}
|
||
|
||
cmd_down() {
|
||
need_cmd docker
|
||
ensure_env
|
||
log "停止容器(保留数据卷)…"
|
||
compose down
|
||
log "已停止。删除数据卷请手动: docker volume rm kefu-cloud_kefu_cloud_pgdata kefu-cloud_kefu_cloud_miniodata"
|
||
}
|
||
|
||
cmd_status() {
|
||
need_cmd docker
|
||
if [[ -f "$ENV_FILE" ]]; then
|
||
compose ps
|
||
echo
|
||
curl -fsS "http://127.0.0.1:${HTTP_PORT:-80}/health" && echo || warn "health 不可达"
|
||
else
|
||
err "缺少 $ENV_FILE"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
main() {
|
||
local action="${1:-up}"
|
||
case "$action" in
|
||
up|--up|"")
|
||
shift || true
|
||
cmd_up "$@"
|
||
;;
|
||
--seed)
|
||
# 兼容:./deploy.sh --seed
|
||
cmd_up --seed
|
||
;;
|
||
--seed-only)
|
||
cmd_seed_only
|
||
;;
|
||
--down|down)
|
||
cmd_down
|
||
;;
|
||
--status|status)
|
||
# 尝试读 HTTP_PORT
|
||
if [[ -f "$ENV_FILE" ]]; then
|
||
# shellcheck disable=SC1090
|
||
set -a; source <(sed 's/\r$//' "$ENV_FILE" | grep -v '^\s*#' | grep -v '^\s*$' || true); set +a
|
||
fi
|
||
cmd_status
|
||
;;
|
||
--pull-only)
|
||
cmd_up --pull-only
|
||
;;
|
||
-h|--help|help)
|
||
sed -n '2,12p' "$0"
|
||
;;
|
||
*)
|
||
err "未知参数: $action (见 --help)"
|
||
exit 1
|
||
;;
|
||
esac
|
||
}
|
||
|
||
main "$@"
|