#!/bin/bash # ============================================================ # live.fuhua91.com 生产服务器基础环境初始化脚本(幂等,可重复执行) # # 功能: # 1. 安装 Docker(自动识别 CentOS/Rocky/Alma/Ubuntu/Debian) # 2. 启动 MySQL 8 容器(root 强密码,外部端口 13306,数据落盘 /www/live-data/mysql) # 3. 启动 Redis 7 容器(强密码,仅容器网络内可访问 + 本机 127.0.0.1:16379 调试口) # 4. 生成宿主机 nginx 站点配置(80/443 -> 容器端口 9501/9502/9503,自签证书启用 https, # 并清理 nginx.conf / conf.d 中旧 live 站点的反代配置) # 5. 自动生成后端配置 /www/wwwroot/live.fuhua91.com/config/config.yaml(写入生成的密码) # 6. 凭据保存到 /root/live-credentials.txt(chmod 600) # # 用法: # bash server-init.sh 仅初始化基础环境 # bash server-init.sh latest 初始化完成后立即用 deploy.sh 部署 latest 版本 # HARBOR_PASSWORD='xxx' bash server-init.sh latest 连 Harbor 密码一起传给 deploy.sh # # 建议: 放到 /root/server-init.sh,执行 chmod 700 /root/server-init.sh # ============================================================ set -euo pipefail # ---------- 可按需调整的参数 ---------- MYSQL_CONTAINER="live-mysql" REDIS_CONTAINER="live-redis" NETWORK="live-net" MYSQL_IMAGE="mysql:8.0" REDIS_IMAGE="redis:7-alpine" DATA_DIR="/www/live-data" # MySQL/Redis/网关配置/证书等落盘目录 MYSQL_EXTERNAL_PORT=13306 # MySQL 对外端口 REDIS_LOCAL_PORT=16379 # Redis 仅绑定 127.0.0.1 的调试端口,不对公网开放 LIVE_HOST="${KRA_LIVE_HOST:-live.yuzou99.com}" HOST_NGINX_CONF="/etc/nginx/conf.d/live.fuhua91.com.conf" # 宿主机 nginx 站点配置 SSL_WEBROOT="$DATA_DIR/ssl/webroot" # acme.sh http-01 验证目录 SSL_CERT_DIR="$DATA_DIR/ssl/certs" # HTTPS 证书目录 BASE_DIR="/www/wwwroot/live.fuhua91.com" CONFIG_DIR="$BASE_DIR/config" # 后端配置目录 -> 容器内 /data/conf CRED_FILE="/root/live-credentials.txt" # Docker Hub 镜像加速(拉不动 mysql/redis 时 docker 会自动回退官方源; # 如你的服务器可直接访问 Docker Hub,可清空此数组) DOCKER_MIRRORS=("https://docker.1ms.run" "https://docker.m.daocloud.io") # -------------------------------------- TAG="${1:-}" # ---------- 通用 ---------- if [ "$(id -u)" -ne 0 ]; then echo "错误: 请用 root 执行本脚本" exit 1 fi # 生成 24 位强密码(字符集避开 MySQL DSN 分隔符 @ : / ? 以及 shell/YAML 敏感字符) gen_pass() { local pass="" while [ "${#pass}" -lt 24 ]; do pass="${pass}$(tr -dc 'A-Za-z0-9#$%^*+_-' < /dev/urandom | head -c 32 || true)" done printf '%s' "${pass:0:24}" } # ---------- 1. 安装 Docker ---------- if [ -r /etc/os-release ]; then . /etc/os-release OS_ID="${ID:-unknown}" OS_MAJOR="${VERSION_ID%%.*}" else OS_ID="unknown" OS_MAJOR="" fi fix_centos7_repos() { echo "==> 检测到 CentOS 7(已 EOL),切换 yum 源到 vault" mkdir -p /etc/yum.repos.d/backup for f in /etc/yum.repos.d/CentOS-*.repo; do [ -e "$f" ] || return 0 mv "$f" /etc/yum.repos.d/backup/ 2>/dev/null || true done curl -fsSL -o /etc/yum.repos.d/CentOS-Base.repo \ https://mirrors.aliyun.com/repo/Centos-7.repo 2>/dev/null || true yum clean all >/dev/null 2>&1 || true } install_docker_rhel() { yum install -y yum-utils 2>/dev/null || yum install -y --allowerasing yum-utils if ! yum-config-manager --add-repo \ https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo; then yum-config-manager --add-repo \ https://download.docker.com/linux/centos/docker-ce.repo fi yum makecache fast >/dev/null 2>&1 || yum makecache >/dev/null 2>&1 || true yum install -y docker-ce docker-ce-cli containerd.io \ || yum install -y --allowerasing docker-ce docker-ce-cli containerd.io } install_docker_deb() { local distro="$1" codename export DEBIAN_FRONTEND=noninteractive apt-get update -y apt-get install -y ca-certificates curl gnupg lsb-release codename="$(lsb_release -cs 2>/dev/null || echo stable)" install -m 0755 -d /etc/apt/keyrings curl -fsSL "https://mirrors.aliyun.com/docker-ce/linux/${distro}/gpg" \ | gpg --dearmor --yes -o /etc/apt/keyrings/docker.gpg chmod a+r /etc/apt/keyrings/docker.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \ https://mirrors.aliyun.com/docker-ce/linux/${distro} ${codename} stable" \ > /etc/apt/sources.list.d/docker.list apt-get update -y apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin } configure_docker() { # 日志轮转 + 镜像加速,避免容器日志撑爆磁盘 [ -f /etc/docker/daemon.json ] && cp /etc/docker/daemon.json "/etc/docker/daemon.json.bak.$(date +%s)" || true local mirrors_json="[]" if [ "${#DOCKER_MIRRORS[@]}" -gt 0 ]; then mirrors_json=$(printf '"%s",' "${DOCKER_MIRRORS[@]}") mirrors_json="[${mirrors_json%,}]" fi cat > /etc/docker/daemon.json </dev/null 2>&1; then echo "==> Docker 已安装: $(docker --version),跳过安装" configure_docker return 0 fi echo "==> 安装 Docker(系统: ${OS_ID} ${OS_MAJOR:-})" case "$OS_ID" in centos|rhel|rocky|almalinux|anolis|opencloudos|tencentos) [ "$OS_MAJOR" = "7" ] && fix_centos7_repos install_docker_rhel ;; ubuntu|debian) install_docker_deb "$OS_ID" ;; *) echo "错误: 未识别的系统 ${OS_ID},请手动安装 Docker 后重新执行本脚本" exit 1 ;; esac systemctl enable --now docker configure_docker echo "==> Docker 安装完成: $(docker --version)" } # ---------- 2. 读取/生成凭据 ---------- MYSQL_ROOT_PASSWORD="" REDIS_PASSWORD="" if [ -f "$CRED_FILE" ]; then MYSQL_ROOT_PASSWORD="$(sed -n "s/^MYSQL_ROOT_PASSWORD='\(.*\)'$/\1/p" "$CRED_FILE")" REDIS_PASSWORD="$(sed -n "s/^REDIS_PASSWORD='\(.*\)'$/\1/p" "$CRED_FILE")" fi if [ -z "$MYSQL_ROOT_PASSWORD" ]; then MYSQL_ROOT_PASSWORD="$(gen_pass)" fi if [ -z "$REDIS_PASSWORD" ]; then REDIS_PASSWORD="$(gen_pass)" fi save_credentials() { cat > "$CRED_FILE" </dev/null 2>&1 || true if docker ps --format '{{.Names}}' | grep -qx "$MYSQL_CONTAINER"; then echo "==> $MYSQL_CONTAINER 已在运行,跳过" return 0 fi # 容器存在但停止:直接启动(数据卷里已有旧密码,不能重置环境变量) if docker ps -a --format '{{.Names}}' | grep -qx "$MYSQL_CONTAINER"; then echo "==> 启动已存在的 $MYSQL_CONTAINER" docker start "$MYSQL_CONTAINER" return 0 fi echo "==> 首次启动 $MYSQL_CONTAINER($MYSQL_IMAGE,外部端口 $MYSQL_EXTERNAL_PORT)" mkdir -p "$DATA_DIR/mysql" docker run -d --name "$MYSQL_CONTAINER" \ --restart always \ --network "$NETWORK" \ -p "${MYSQL_EXTERNAL_PORT}:3306" \ -e TZ=Asia/Shanghai \ -e MYSQL_ROOT_PASSWORD="$MYSQL_ROOT_PASSWORD" \ -e MYSQL_DATABASE=yzyzlive \ -v "$DATA_DIR/mysql:/var/lib/mysql" \ "$MYSQL_IMAGE" \ --character-set-server=utf8mb4 \ --collation-server=utf8mb4_general_ci \ --default-authentication-plugin=mysql_native_password echo "==> 等待 MySQL 就绪(首次启动约 20-60 秒)" local i ready=0 for i in $(seq 1 60); do if docker exec "$MYSQL_CONTAINER" mysqladmin ping -uroot -p"$MYSQL_ROOT_PASSWORD" --silent >/dev/null 2>&1; then ready=1 break fi sleep 3 done if [ "$ready" -ne 1 ]; then echo "错误: MySQL 未就绪,请执行 docker logs $MYSQL_CONTAINER 排查" exit 1 fi echo "==> MySQL 就绪(数据库 yzyzlive 已创建)" } # ---------- 4. Redis ---------- run_redis() { if docker ps --format '{{.Names}}' | grep -qx "$REDIS_CONTAINER"; then echo "==> $REDIS_CONTAINER 已在运行,跳过" return 0 fi if docker ps -a --format '{{.Names}}' | grep -qx "$REDIS_CONTAINER"; then echo "==> 启动已存在的 $REDIS_CONTAINER" docker start "$REDIS_CONTAINER" return 0 fi echo "==> 首次启动 $REDIS_CONTAINER($REDIS_IMAGE,不对公网开放)" mkdir -p "$DATA_DIR/redis" docker run -d --name "$REDIS_CONTAINER" \ --restart always \ --network "$NETWORK" \ -p "127.0.0.1:${REDIS_LOCAL_PORT}:6379" \ -e TZ=Asia/Shanghai \ -v "$DATA_DIR/redis:/data" \ "$REDIS_IMAGE" \ redis-server --requirepass "$REDIS_PASSWORD" --appendonly yes local i for i in $(seq 1 10); do if docker exec "$REDIS_CONTAINER" redis-cli -a "$REDIS_PASSWORD" --no-auth-warning ping \ 2>/dev/null | grep -q PONG; then echo "==> Redis 就绪(本机调试口 127.0.0.1:${REDIS_LOCAL_PORT})" return 0 fi sleep 2 done echo "错误: Redis 启动异常,请执行 docker logs $REDIS_CONTAINER 排查" exit 1 } # ---------- 4.5 宿主机 nginx 站点配置(80/443 -> 容器端口 9501/9502/9503) ---------- # 清理 nginx.conf 及 conf.d 中旧站点(live.fuhua91.com / live.yuzhou99.com / live.yuzou99.com / test-zb.yzyzzb.com)的反代配置(整块删除含目标域名的 server 块) clean_old_nginx_proxy() { local f=/etc/nginx/nginx.conf if [ -f "$f" ] && grep -qE 'live\.(fuhua91|yuzhou99|yuzou99)\.com|test-zb\.yzyzzb\.com' "$f"; then cp "$f" "$f.bak.$(date +%s)" awk ' BEGIN { in_server=0; depth=0 } !in_server { if ($0 ~ /^[[:space:]]*server[[:space:]]*\{/) { in_server=1; depth=1; buf=$0 "\n"; next } print; next } in_server { buf = buf $0 "\n" o = gsub(/\{/, "{"); c = gsub(/\}/, "}") depth += o - c if (depth <= 0) { if (buf !~ /live\.(fuhua91|yuzhou99|yuzou99)\.com|test-zb\.yzyzzb\.com/) printf "%s", buf in_server=0; buf="" } } ' "$f" > "$f.tmp" && mv "$f.tmp" "$f" echo "==> 已从 nginx.conf 移除旧 live 站点反代配置(备份: $f.bak.*)" fi local cf my_conf my_ssl_conf my_conf="$HOST_NGINX_CONF" my_ssl_conf="${HOST_NGINX_CONF%.conf}.ssl.conf" for cf in /etc/nginx/conf.d/*.conf; do [ -e "$cf" ] || continue [ "$cf" = "$my_conf" ] && continue [ "$cf" = "$my_ssl_conf" ] && continue if grep -qE 'live\.(fuhua91|yuzhou99|yuzou99)\.com|test-zb\.yzyzzb\.com' "$cf"; then mv "$cf" "$cf.bak.$(date +%s)" echo "==> 已移除旧站点配置: $cf(备份为 .bak.*)" fi done } write_host_nginx_conf() { # 清理旧方案的网关容器(如曾创建) docker rm -f live-gateway >/dev/null 2>&1 || true mkdir -p "$SSL_WEBROOT" "$SSL_CERT_DIR" if ! command -v nginx >/dev/null 2>&1; then echo "警告: 宿主机未安装 nginx,跳过站点配置。请安装后重新执行本脚本:" if command -v apt-get >/dev/null 2>&1; then echo " apt-get install -y nginx" else echo " yum install -y nginx && systemctl enable --now nginx" fi return 0 fi # 移除 nginx.conf / conf.d 中旧 live 站点的反代配置,避免冲突 clean_old_nginx_proxy # 确保 nginx.conf include 了 conf.d/*.conf(旧反代直接写在 nginx.conf 的机器常缺这条) if ! grep -Eq 'include[[:space:]]+.*conf\.d/\*\.conf' /etc/nginx/nginx.conf; then mkdir -p /etc/nginx/conf.d if grep -qE '^[[:space:]]*http[[:space:]]*\{' /etc/nginx/nginx.conf; then cp /etc/nginx/nginx.conf "/etc/nginx/nginx.conf.bak.$(date +%s)" sed -i '/^[[:space:]]*http[[:space:]]*\{/a\ include /etc/nginx/conf.d/*.conf;' /etc/nginx/nginx.conf echo "==> 已在 nginx.conf 的 http {} 中加入 include /etc/nginx/conf.d/*.conf" else echo "警告: nginx.conf 中未找到 http {} 块,请手动加入 include conf.d/*.conf 后重载" fi fi [ -f "$HOST_NGINX_CONF" ] && cp "$HOST_NGINX_CONF" "$HOST_NGINX_CONF.bak.$(date +%s)" echo "==> 写入宿主机 nginx 站点配置: $HOST_NGINX_CONF" cat > "$HOST_NGINX_CONF" <<'NGINXCONF' # live.fuhua91.com 站点配置(server-init.sh 生成;HTTPS 模板见 live.fuhua91.com.ssl.conf.example) # 应用容器端口(deploy.sh 映射): admin=9501, 后端=9502, 主站=9503 # 变量名带 live_ 前缀,避免与宿主机 nginx 其他配置中的 map 重名冲突 map $http_upgrade $live_connection_upgrade { default upgrade; '' close; } server { listen 80 default_server; server_name live.fuhua91.com; # 启用 HTTPS 后取消下行注释(acme 续期会经 301 走 https 验证,仍可用) #return 301 https://$host$request_uri; # admin 媒体上传,默认 1m 会挡上传 client_max_body_size 512m; # CORS 全放行:反射 Origin(兼容带 cookie 的请求,* 与 credentials 不兼容) add_header Access-Control-Allow-Origin $http_origin always; add_header Access-Control-Allow-Credentials true always; add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS' always; add_header Access-Control-Allow-Headers $http_access_control_request_headers always; add_header Vary Origin always; # 隐藏后端自带的 CORS header,避免与上面重复导致浏览器报错 proxy_hide_header Access-Control-Allow-Origin; proxy_hide_header Access-Control-Allow-Credentials; proxy_hide_header Access-Control-Allow-Methods; proxy_hide_header Access-Control-Allow-Headers; proxy_hide_header Access-Control-Expose-Headers; proxy_hide_header Access-Control-Max-Age; # 预检请求直接响应,不转发后端(if 块的 add_header 会覆盖上面的,需写全) if ($request_method = OPTIONS) { add_header Access-Control-Allow-Origin $http_origin always; add_header Access-Control-Allow-Credentials true always; add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS' always; add_header Access-Control-Allow-Headers $http_access_control_request_headers always; add_header Access-Control-Max-Age 86400 always; add_header Vary Origin always; return 204; } # acme.sh http-01 验证 location ^~ /.well-known/acme-challenge/ { root /www/live-data/ssl/webroot; } # 后端 API / WebSocket(去掉 /api 前缀转给后端容器 9502) location = /api { proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $live_connection_upgrade; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass http://127.0.0.1:9502; } location ^~ /api/ { proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $live_connection_upgrade; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 600s; proxy_send_timeout 600s; rewrite ^/api/(.*)$ /$1 break; proxy_pass http://127.0.0.1:9502; } # admin 管理后台(去掉 /admin 前缀转给 admin 前端容器 9501) location = /admin { return 301 /admin/; } location ^~ /admin/ { proxy_http_version 1.1; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; rewrite ^/admin/(.*)$ /$1 break; proxy_pass http://127.0.0.1:9501; } # 主站直播页(live-web 容器 9503) location / { proxy_http_version 1.1; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass http://127.0.0.1:9503; } } NGINXCONF # HTTPS 模板(.ssl.conf.example 后缀不会被 *.conf include,复制为 .ssl.conf 后生效) echo "==> 写入 HTTPS 配置模板: ${HOST_NGINX_CONF%.conf}.ssl.conf.example" cat > "${HOST_NGINX_CONF%.conf}.ssl.conf.example" <<'SSLEX' # 启用 HTTPS 三步: # 1. 证书安装到 /www/live-data/ssl/certs/{fullchain.pem,privkey.pem} # 2. cp live.fuhua91.com.ssl.conf.example live.fuhua91.com.ssl.conf # 3. nginx -t && systemctl reload nginx # 4. 在 live.fuhua91.com.conf 打开 return 301 注释后再 reload 一次 server { listen 443 ssl http2 default_server; server_name live.fuhua91.com; ssl_certificate /www/live-data/ssl/certs/fullchain.pem; ssl_certificate_key /www/live-data/ssl/certs/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; client_max_body_size 512m; # CORS 全放行(同 80 端口 server 块) add_header Access-Control-Allow-Origin $http_origin always; add_header Access-Control-Allow-Credentials true always; add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS' always; add_header Access-Control-Allow-Headers $http_access_control_request_headers always; add_header Vary Origin always; proxy_hide_header Access-Control-Allow-Origin; proxy_hide_header Access-Control-Allow-Credentials; proxy_hide_header Access-Control-Allow-Methods; proxy_hide_header Access-Control-Allow-Headers; proxy_hide_header Access-Control-Expose-Headers; proxy_hide_header Access-Control-Max-Age; if ($request_method = OPTIONS) { add_header Access-Control-Allow-Origin $http_origin always; add_header Access-Control-Allow-Credentials true always; add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS' always; add_header Access-Control-Allow-Headers $http_access_control_request_headers always; add_header Access-Control-Max-Age 86400 always; add_header Vary Origin always; return 204; } # acme.sh http-01 验证(续期会经 301 走 https 验证,仍可用) location ^~ /.well-known/acme-challenge/ { root /www/live-data/ssl/webroot; } location = /api { proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $live_connection_upgrade; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass http://127.0.0.1:9502; } location ^~ /api/ { proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $live_connection_upgrade; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 600s; proxy_send_timeout 600s; rewrite ^/api/(.*)$ /$1 break; proxy_pass http://127.0.0.1:9502; } location = /admin { return 301 /admin/; } location ^~ /admin/ { proxy_http_version 1.1; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; rewrite ^/admin/(.*)$ /$1 break; proxy_pass http://127.0.0.1:9501; } location / { proxy_http_version 1.1; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass http://127.0.0.1:9503; } } SSLEX # 域名统一替换(默认 live.yuzou99.com,可用 KRA_LIVE_HOST 覆盖) sed -i "s/live\.fuhua91\.com/${LIVE_HOST}/g" "$HOST_NGINX_CONF" sed -i "s/live\.fuhua91\.com/${LIVE_HOST}/g" "${HOST_NGINX_CONF%.conf}.ssl.conf.example" # 自签证书(已存在则复用;浏览器提示不安全属正常,正式证书可后补) if [ ! -f "$SSL_CERT_DIR/fullchain.pem" ]; then echo "==> 生成自签证书: $LIVE_HOST(10 年有效期)" openssl req -x509 -nodes -newkey rsa:2048 -days 3650 \ -keyout "$SSL_CERT_DIR/privkey.pem" -out "$SSL_CERT_DIR/fullchain.pem" \ -subj "/CN=${LIVE_HOST}" \ -addext "subjectAltName=DNS:${LIVE_HOST}" 2>/dev/null \ || openssl req -x509 -nodes -newkey rsa:2048 -days 3650 \ -keyout "$SSL_CERT_DIR/privkey.pem" -out "$SSL_CERT_DIR/fullchain.pem" \ -subj "/CN=${LIVE_HOST}" chmod 600 "$SSL_CERT_DIR/privkey.pem" fi # 启用 443(自签证书)+ 80 强跳 https cp -f "${HOST_NGINX_CONF%.conf}.ssl.conf.example" "${HOST_NGINX_CONF%.conf}.ssl.conf" sed -i 's|^ #return 301 https://| return 301 https://|' "$HOST_NGINX_CONF" # SELinux 开启时允许 nginx 反代网络端口(CentOS/Rocky 常见 502 原因) if command -v getenforce >/dev/null 2>&1 && [ "$(getenforce 2>/dev/null)" = "Enforcing" ]; then setsebool -P httpd_can_network_connect 1 2>/dev/null \ || echo "警告: setsebool 失败,nginx 反代容器端口可能 502" fi if nginx -t 2>/dev/null; then systemctl enable nginx >/dev/null 2>&1 || true if systemctl is-active --quiet nginx; then systemctl reload nginx 2>/dev/null || nginx -s reload 2>/dev/null || true else systemctl start nginx 2>/dev/null || true fi echo "==> nginx 配置已生效(80/443 -> 9503 主站 / 9501 admin / 9502 api,https 为自签证书)" else echo "警告: nginx -t 校验未通过,请手动检查后重载:" nginx -t || true echo " nginx -t && systemctl reload nginx" fi } # ---------- 5. 生成后端配置 ---------- write_backend_config() { mkdir -p "$CONFIG_DIR" if [ ! -f "$CONFIG_DIR/config.yaml" ]; then echo "==> 生成后端配置: $CONFIG_DIR/config.yaml" cat > "$CONFIG_DIR/config.yaml" <<'CONF' server: http: network: tcp addr: 0.0.0.0:8000 timeout: 600s data: database: driver: mysql source: "" host: live-mysql port: "3306" user: root password: '__MYSQL_ROOT_PASSWORD__' name: yzyzlive config: charset=utf8mb4&parseTime=True&loc=Local path: "" alias_name: "" disable: false prefix: "" engine: InnoDB log_mode: info max_idle_conns: 10 max_open_conns: 100 conn_max_lifetime: 3600 singular: false redis: network: tcp name: default addr: live-redis:6379 password: '__REDIS_PASSWORD__' db: 0 use_cluster: false cluster_addrs: [] read_timeout: 0.200s write_timeout: 0.200s database_list: [] redis_list: [] mongo: coll: "" options: "" database: "" username: "" password: "" auth_source: "" min_pool_size: "0" max_pool_size: "100" socket_timeout_ms: "0" connect_timeout_ms: "0" is_zap: false hosts: - host: "" port: "" admin: router_prefix: "" jwt: signing_key: '__JWT_SIGNING_KEY__' expires_time: 604800s buffer_time: 86400s issuer: kra captcha: key_long: 6 img_width: 240 img_height: 80 store_expiration: 180s local: store_path: uploads/file path_prefix: uploads/file media: session_ttl: 24 max_file_size: "0" chunk_dir: uploads/chunks system: use_redis: false use_multipoint: false use_strict_auth: false disable_auto_migrate: false use_mongo: false addr: 8000 iplimit_count: 0 iplimit_time: 0 zap: level: info prefix: '[kra] ' format: json director: logs encode_level: LowercaseLevelEncoder stacktrace_key: stacktrace show_line: true log_in_console: true retention_day: 7 access_req_body: true access_resp_data: true access_req_headers: true access_log_max_bytes: 1024 file_only_modules: - http - sql cors: mode: allow-all whitelist: [] app: node: "" app_id: kra env: production disk_list: - mount_point: / CONF sed -i "s|__MYSQL_ROOT_PASSWORD__|${MYSQL_ROOT_PASSWORD}|; s|__REDIS_PASSWORD__|${REDIS_PASSWORD}|; s|__JWT_SIGNING_KEY__|$(gen_pass)|" \ "$CONFIG_DIR/config.yaml" else echo "==> 后端配置已存在,跳过生成: $CONFIG_DIR/config.yaml" fi # 容器内进程以 uid 1000 (kra) 运行: # - 配置文件必须对其可读,否则 panic: permission denied # - 配置目录必须对其可写,否则首装向导无法把生成的 JWT signing_key 写回文件 chown -R 1000:1000 "$CONFIG_DIR" chmod 600 "$CONFIG_DIR/config.yaml" } # ---------- 6. 放行防火墙端口(尽力而为) ---------- open_firewall() { local ports=("13306/tcp" "80/tcp" "443/tcp" "9504/udp") echo "==> 尝试放行防火墙端口 ${ports[*]}(云安全组需自行放行)" local p for p in "${ports[@]}"; do if command -v firewall-cmd >/dev/null 2>&1 && firewall-cmd --state >/dev/null 2>&1; then firewall-cmd --permanent --add-port="${p}" >/dev/null 2>&1 || true firewall-cmd --reload >/dev/null 2>&1 || true fi if command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -q "Status: active"; then ufw allow "${p}" >/dev/null 2>&1 || true fi done } # ---------- 主流程 ---------- install_docker save_credentials run_mysql run_redis write_backend_config write_host_nginx_conf open_firewall echo "" echo "============================================================" echo " 基础环境初始化完成" echo "============================================================" echo " MySQL: live-mysql 容器, 外部端口 $MYSQL_EXTERNAL_PORT, 数据目录 $DATA_DIR/mysql" echo " Redis: live-redis 容器, 仅容器网络 + 127.0.0.1:$REDIS_LOCAL_PORT" echo " nginx: 宿主机 nginx 站点配置 -> 主站(9503)/admin(9501)/api(9502)" echo " HTTPS: 已用自签证书启用($LIVE_HOST),浏览器提示不安全属正常" echo " 后端配置: $CONFIG_DIR/config.yaml" echo " 凭据文件: $CRED_FILE (MySQL root / Redis 密码都在里面)" echo "" echo " 云服务器安全组请放行: $MYSQL_EXTERNAL_PORT/tcp, 80/tcp, 443/tcp, 9504/udp(WebRTC)" echo " 请确认 DNS: $LIVE_HOST 已解析到本机公网 IP" echo "" echo " 换正式证书(可选,替换自签证书,同路径自动 reload):" echo " curl https://get.acme.sh | sh -s email=you@example.com" echo " ~/.acme.sh/acme.sh --issue -d $LIVE_HOST -w $SSL_WEBROOT --server letsencrypt" echo " ~/.acme.sh/acme.sh --install-cert -d $LIVE_HOST \\" echo " --fullchain-file $SSL_CERT_DIR/fullchain.pem \\" echo " --key-file $SSL_CERT_DIR/privkey.pem \\" echo " --reloadcmd 'nginx -s reload'" echo "" if [ -n "$TAG" ]; then echo "==> 继续部署应用版本: $TAG" DEPLOY_SH="$(cd "$(dirname "$0")" && pwd)/deploy.sh" [ -f "$DEPLOY_SH" ] || DEPLOY_SH="/root/deploy.sh" if [ -f "$DEPLOY_SH" ]; then bash "$DEPLOY_SH" "$TAG" else echo "错误: 找不到 deploy.sh(与 server-init.sh 同目录或 /root/deploy.sh),请手动执行部署" exit 1 fi else echo " 下一步: 上传 deploy.sh 到服务器后执行(Harbor 需先登录):" echo " docker login harbor.fuhua91.com -u 'robot\$live+live'" echo " bash /root/deploy.sh " fi