#!/usr/bin/env bash # 宝塔宿主机 MySQL + Docker 应用 — 192.168.2.14:30327 # 用法(/home/project/yubao): # bash deploy/install-bt-host-mysql.sh # 若尚无 .env,会自动从 deploy/.env.bt-host-mysql.example 复制 set -euo pipefail ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" COMPOSE_FILE="${ROOT_DIR}/deploy/docker-compose.bt-host-mysql.yml" ENV_FILE="${ROOT_DIR}/.env" RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' log() { echo -e "${GREEN}[deploy-bt]${NC} $*"; } warn() { echo -e "${YELLOW}[deploy-bt]${NC} $*"; } err() { echo -e "${RED}[deploy-bt]${NC} $*" >&2; } read_env() { local key="$1" local line line="$(grep -E "^[[:space:]]*${key}=" "$ENV_FILE" 2>/dev/null | tail -1 || true)" if [[ -z "$line" ]]; then echo "" return fi line="${line#*=}" line="${line#"${line%%[![:space:]]*}"}" line="${line%"${line##*[![:space:]]}"}" if [[ "$line" == \"*\" && "$line" == *\" ]]; then line="${line:1:${#line}-2}" elif [[ "$line" == \'*\' && "$line" == *\' ]]; then line="${line:1:${#line}-2}" fi echo "$line" } validate_env() { local example="${ROOT_DIR}/deploy/.env.bt-host-mysql.example" if [[ ! -f "$ENV_FILE" ]]; then if [[ -f "$example" ]]; then log "未找到 .env,从 deploy/.env.bt-host-mysql.example 自动复制" cp "$example" "$ENV_FILE" else err "未找到 .env 且缺少 ${example}" exit 1 fi fi DATABASE_URL="$(read_env DATABASE_URL)" SESSION_SECRET="$(read_env SESSION_SECRET)" APP_ADMIN_PASS="$(read_env APP_ADMIN_PASS)" APP_OPS_PASS="$(read_env APP_OPS_PASS)" APP_PORT="$(read_env APP_PORT)" PUBLIC_HOST="$(read_env PUBLIC_HOST)" MYSQL_USER="$(read_env MYSQL_USER)" MYSQL_PASSWORD="$(read_env MYSQL_PASSWORD)" local missing=0 for key in DATABASE_URL SESSION_SECRET APP_ADMIN_PASS APP_OPS_PASS MYSQL_USER MYSQL_PASSWORD; do local val="${!key:-}" if [[ -z "$val" ]] || [[ "$val" == CHANGE_ME* ]]; then err "请在 .env 中填写: $key" missing=1 fi done if [[ "$APP_ADMIN_PASS" == "$APP_OPS_PASS" ]]; then err "APP_ADMIN_PASS 与 APP_OPS_PASS 不能相同" missing=1 fi for weak in admin123 ops123 password 123456; do if [[ "$APP_ADMIN_PASS" == "$weak" || "$APP_OPS_PASS" == "$weak" ]]; then err "生产环境禁止使用弱口令: $weak" missing=1 fi done if [[ "$SESSION_SECRET" == *"change-me"* ]] || [[ ${#SESSION_SECRET} -lt 32 ]]; then err "SESSION_SECRET 须至少 32 字符且非开发默认值" missing=1 fi if [[ $missing -ne 0 ]]; then exit 1 fi } preflight_mysql() { local db_user db_pass db_name db_user="$(read_env MYSQL_USER)" db_pass="$(read_env MYSQL_PASSWORD)" db_name="$(read_env MYSQL_DATABASE)" if [[ -z "$db_pass" ]]; then return 0 fi log "测试宝塔 MySQL 连接..." if docker run --rm --add-host=host.docker.internal:host-gateway mysql:8 \ mysql -h host.docker.internal -u "${db_user}" -p"${db_pass}" -e "SELECT 1" "${db_name}" >/dev/null 2>&1; then log "MySQL 连接正常" return 0 fi err "MySQL 认证失败:请核对 .env 中 MYSQL_PASSWORD / DATABASE_URL 是否与宝塔一致" err "常见原因:" err " 1) 宝塔用户未授权 Docker 网段(172.%)" err " 2) 重新上传代码覆盖了 .env" err "修复示例:" err " MYSQL_PASSWORD=宝塔里 ${db_user} 用户的真实密码" err " DATABASE_URL=mysql://${db_user}:真实密码@host.docker.internal:3306/${db_name}" exit 1 } deploy_docker() { if ! command -v docker >/dev/null 2>&1; then err "未安装 Docker" exit 1 fi if ! docker compose version >/dev/null 2>&1; then err "需要 Docker Compose V2" exit 1 fi mkdir -p "${ROOT_DIR}/data" cd "$ROOT_DIR" local compose=(docker compose --env-file "$ENV_FILE" -f "$COMPOSE_FILE") log "构建并启动(无容器 MySQL,连接宝塔 MySQL)..." export DOCKER_BUILDKIT=1 export BUILDKIT_MAX_PARALLELISM=1 "${compose[@]}" build web "${compose[@]}" up -d log "同步数据库结构(prisma db push)..." "${compose[@]}" run --rm --no-deps web pnpm exec prisma db push --skip-generate log "同步默认账号(admin / ops)..." "${compose[@]}" run --rm --no-deps web pnpm db:seed log "容器状态:" "${compose[@]}" ps } smoke_check() { local port="${APP_PORT:-30327}" local url="http://127.0.0.1:${port}/api/health" log "健康检查: ${url}" local body body="$(curl -sf "$url" 2>/dev/null || true)" if [[ "$body" == \{* ]]; then log "Web 服务正常" else warn "Web 暂未响应,请查看:docker compose -f deploy/docker-compose.bt-host-mysql.yml logs -f web" fi } print_summary() { local port="${APP_PORT:-30327}" local host="${PUBLIC_HOST:-<服务器内网IP>}" echo "" log "========== 部署完成(宝塔 MySQL 模式)==========" echo " 内网访问: http://${host}:${port}" echo " 登录页: http://${host}:${port}/login" echo " 邮件列表: http://${host}:${port}/mails" echo " 设置页: http://${host}:${port}/settings" echo "" echo " 常用命令:" echo " docker compose --env-file .env -f deploy/docker-compose.bt-host-mysql.yml logs -f worker" echo " docker compose --env-file .env -f deploy/docker-compose.bt-host-mysql.yml restart" echo "" warn "首次登录后请立即修改 admin/ops 密码(.env 中 APP_ADMIN_PASS / APP_OPS_PASS)" warn "CC / IMAP 推荐在 Admin「设置」页配置,不必改代码" } main() { validate_env preflight_mysql deploy_docker smoke_check print_summary } main "$@"