Files
kra-new/deploy/server-init.sh
T
2026-09-23 18:20:17 +08:00

771 lines
29 KiB
Bash
Raw 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.
#!/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 <<EOF
{
"log-driver": "json-file",
"log-opts": { "max-size": "50m", "max-file": "3" },
"registry-mirrors": ${mirrors_json}
}
EOF
systemctl daemon-reload
systemctl restart docker
}
install_docker() {
if command -v docker >/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" <<EOF
# ============================================================
# live.fuhua91.com 生产环境凭据(生成于 $(date '+%F %T'))
# 请妥善保管,勿提交到代码仓库
# ============================================================
MYSQL_ROOT_PASSWORD='${MYSQL_ROOT_PASSWORD}'
MYSQL_EXTERNAL_PORT='${MYSQL_EXTERNAL_PORT}'
REDIS_PASSWORD='${REDIS_PASSWORD}'
BACKEND_CONFIG='${CONFIG_DIR}/config.yaml'
EOF
chmod 600 "$CRED_FILE"
}
# ---------- 3. MySQL ----------
run_mysql() {
docker network create "$NETWORK" >/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 <tag>"
fi