From 34181e36147ba80934230a68a7d08e0415c740b1 Mon Sep 17 00:00:00 2001 From: shunli Date: Sun, 22 Mar 2026 13:51:14 +0800 Subject: [PATCH] =?UTF-8?q?R24-1=20R24-2:=20=E8=A1=A5=E9=BD=90=E5=90=8C?= =?UTF-8?q?=E6=9C=BA=E9=83=A8=E7=BD=B2=E8=84=9A=E6=9C=AC=E4=B8=8E=20Nginx?= =?UTF-8?q?=20=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .dockerignore | 16 ++++ deploy/trialrun/.env.example | 11 ++- deploy/trialrun/deploy.sh | 114 +++++++++++++++++++++++++++++ deploy/trialrun/docker-compose.yml | 6 +- deploy/trialrun/nginx-hyb-oa.conf | 63 ++++++++++++++++ deploy/trialrun/nginx.conf.example | 8 +- deploy/trialrun/setup-server.sh | 62 ++++++++++++++++ 7 files changed, 270 insertions(+), 10 deletions(-) create mode 100644 .dockerignore create mode 100755 deploy/trialrun/deploy.sh create mode 100644 deploy/trialrun/nginx-hyb-oa.conf create mode 100755 deploy/trialrun/setup-server.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..b1694c45 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,16 @@ +.git +.github +.agent +.agents +.codex +.claude +node_modules +client/node_modules +server/node_modules +client/test-results +client/playwright-report +server/generated +deploy/trialrun/data +coverage +dist +*.log diff --git a/deploy/trialrun/.env.example b/deploy/trialrun/.env.example index ed824391..cf4282d9 100644 --- a/deploy/trialrun/.env.example +++ b/deploy/trialrun/.env.example @@ -4,10 +4,17 @@ ## cp deploy/trialrun/.env.example deploy/trialrun/.env ## 然后按需修改 deploy/trialrun/.env 中的值(不要提交 .env) ## +## 同机公网部署建议: +## - 官网继续使用 hyinda.com / www.hyinda.com +## - OA 单独使用 oa.example.com 一类二级域名 +## - 正式公网环境请保持 APP_ORIGIN=https://oa.example.com +## - 正式公网环境请保持 COOKIE_SECURE=true +## - 若仅做本机临时演练,再改回 http://127.0.0.1:3000 和 COOKIE_SECURE=false +## # ===== 基础域名(用于 refresh cookie 的 origin 校验)===== # 单域托管模式下,前端与后端同域访问(建议生产/试运行)。 -APP_ORIGIN="http://127.0.0.1:3000" +APP_ORIGIN="https://oa.example.com" # ===== 服务端密钥(必须修改)===== JWT_SECRET="change_me_in_trialrun" @@ -68,7 +75,7 @@ HOST="0.0.0.0" PORT="3000" # ===== Cookie 安全(HTTPS 反代时建议 true)===== -COOKIE_SECURE="false" +COOKIE_SECURE="true" # ===== Metrics(Prometheus)===== METRICS_ENABLED="true" diff --git a/deploy/trialrun/deploy.sh b/deploy/trialrun/deploy.sh new file mode 100755 index 00000000..9f8b86e1 --- /dev/null +++ b/deploy/trialrun/deploy.sh @@ -0,0 +1,114 @@ +#!/usr/bin/env bash +# ============================================================ +# hyb-oa - 同机部署脚本(本地执行) +# ============================================================ +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +DEPLOY_SERVER="${DEPLOY_SERVER:-deploy@118.89.77.218}" +REMOTE_DIR="${REMOTE_DIR:-/opt/hyb-oa-trialrun}" +REMOTE_ENV_FILE="${REMOTE_ENV_FILE:-${REMOTE_DIR}/deploy/trialrun/.env}" +ENABLE_WORKER="${ENABLE_WORKER:-0}" +ENABLE_MINIO="${ENABLE_MINIO:-0}" +SYNC_ENV="${SYNC_ENV:-0}" +COMPOSE_FILE="deploy/trialrun/docker-compose.yml" +LOCAL_ENV_FILE="deploy/trialrun/.env" + +need_cmd() { + command -v "$1" >/dev/null 2>&1 || { + echo "[ERROR] 缺少命令: $1" >&2 + exit 1 + } +} + +need_cmd rsync +need_cmd ssh + +if [[ ! -f "${ROOT_DIR}/${COMPOSE_FILE}" ]]; then + echo "[ERROR] 缺少 ${COMPOSE_FILE}" >&2 + exit 1 +fi + +if [[ "${SYNC_ENV}" == "1" && ! -f "${ROOT_DIR}/${LOCAL_ENV_FILE}" ]]; then + echo "[ERROR] SYNC_ENV=1 但本地不存在 ${LOCAL_ENV_FILE}" >&2 + exit 1 +fi + +echo ">>> 1. 创建远端目录" +ssh "${DEPLOY_SERVER}" "mkdir -p '${REMOTE_DIR}'" + +echo ">>> 2. 同步 OA 仓库到服务器" +rsync -az --delete \ + --exclude '.git' \ + --exclude '.github' \ + --exclude '.agent' \ + --exclude '.agents' \ + --exclude '.codex' \ + --exclude '.claude' \ + --exclude 'node_modules' \ + --exclude 'client/node_modules' \ + --exclude 'server/node_modules' \ + --exclude 'client/test-results' \ + --exclude 'client/playwright-report' \ + --exclude 'server/generated' \ + --exclude 'deploy/trialrun/data' \ + --exclude 'deploy/trialrun/.env' \ + --exclude 'coverage' \ + --exclude 'dist' \ + --exclude '*.log' \ + "${ROOT_DIR}/" "${DEPLOY_SERVER}:${REMOTE_DIR}/" + +if [[ "${SYNC_ENV}" == "1" ]]; then + echo ">>> 3. 同步部署环境变量" + ssh "${DEPLOY_SERVER}" "mkdir -p '$(dirname "${REMOTE_ENV_FILE}")'" + rsync -az "${ROOT_DIR}/${LOCAL_ENV_FILE}" "${DEPLOY_SERVER}:${REMOTE_ENV_FILE}" +else + echo ">>> 3. 跳过 .env 同步(默认安全模式)" + echo " 如果你确认本地 deploy/trialrun/.env 可直接上服务器,可用:SYNC_ENV=1 bash deploy/trialrun/deploy.sh" +fi + +echo ">>> 4. 在服务器执行部署" +ssh "${DEPLOY_SERVER}" \ + "REMOTE_DIR='${REMOTE_DIR}' REMOTE_ENV_FILE='${REMOTE_ENV_FILE}' COMPOSE_FILE='${COMPOSE_FILE}' ENABLE_WORKER='${ENABLE_WORKER}' ENABLE_MINIO='${ENABLE_MINIO}' bash -s" <<'REMOTE' +set -euo pipefail +cd "$REMOTE_DIR" +mkdir -p deploy/trialrun/data/postgres deploy/trialrun/data/storage deploy/trialrun/data/minio + +if [[ ! -f "$REMOTE_ENV_FILE" ]]; then + echo "[ERROR] 远端缺少 $REMOTE_ENV_FILE,请先手工创建或使用 SYNC_ENV=1" >&2 + exit 1 +fi + +if docker compose version >/dev/null 2>&1; then + compose_cmd=(docker compose -f "$COMPOSE_FILE" --env-file "$REMOTE_ENV_FILE") +elif command -v docker-compose >/dev/null 2>&1; then + compose_cmd=(docker-compose -f "$COMPOSE_FILE" --env-file "$REMOTE_ENV_FILE") +else + echo "[ERROR] 远端既没有 docker compose,也没有 docker-compose" >&2 + exit 1 +fi + +profile_args=() +if [[ "$ENABLE_MINIO" == "1" ]]; then + profile_args+=(--profile minio) +fi +if [[ "$ENABLE_WORKER" == "1" ]]; then + profile_args+=(--profile worker) +fi + +"${compose_cmd[@]}" up -d db +if [[ "$ENABLE_MINIO" == "1" ]]; then + "${compose_cmd[@]}" "${profile_args[@]}" up -d minio minio_init +fi +"${compose_cmd[@]}" "${profile_args[@]}" up -d --build server +if [[ "$ENABLE_WORKER" == "1" ]]; then + "${compose_cmd[@]}" "${profile_args[@]}" up -d worker +fi +"${compose_cmd[@]}" run --rm init +"${compose_cmd[@]}" ps +REMOTE + +echo ">>> 5. 部署完成" +echo " 远端目录: ${REMOTE_DIR}" +echo " 环境文件: ${REMOTE_ENV_FILE}" +echo " 请确认 Nginx 已把 OA 域名反代到 127.0.0.1:3000" diff --git a/deploy/trialrun/docker-compose.yml b/deploy/trialrun/docker-compose.yml index 204568d5..2a1fe97b 100644 --- a/deploy/trialrun/docker-compose.yml +++ b/deploy/trialrun/docker-compose.yml @@ -43,11 +43,11 @@ services: entrypoint: ["sh", "-c"] command: | set -e - for i in $(seq 1 60); do - mc alias set local http://minio:9000 "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" && break || true + for i in $$(seq 1 60); do + mc alias set local http://minio:9000 "$$MINIO_ROOT_USER" "$$MINIO_ROOT_PASSWORD" && break || true sleep 1 done - mc mb --ignore-existing "local/$S3_BUCKET" + mc mb --ignore-existing "local/$$S3_BUCKET" server: build: diff --git a/deploy/trialrun/nginx-hyb-oa.conf b/deploy/trialrun/nginx-hyb-oa.conf new file mode 100644 index 00000000..4033a5b2 --- /dev/null +++ b/deploy/trialrun/nginx-hyb-oa.conf @@ -0,0 +1,63 @@ +# hyb-oa 生产反向代理(同机官网服务器) +# 使用方式: +# 1) 复制到 /etc/nginx/sites-available/hyb-oa +# 2) 将 server_name / 证书路径改成你的实际值 +# 3) ln -sf /etc/nginx/sites-available/hyb-oa /etc/nginx/sites-enabled/hyb-oa +# 4) nginx -t && systemctl reload nginx + +limit_req_zone $binary_remote_addr zone=oa_general:10m rate=10r/s; + +upstream hyb_oa_upstream { + server 127.0.0.1:3000; + keepalive 32; +} + +server { + listen 80; + server_name oa.hyinda.com; + return 301 https://$host$request_uri; +} + +server { + listen 443 ssl http2; + server_name oa.hyinda.com; + + ssl_certificate /etc/nginx/ssl/oa.hyinda.com/server.crt; + ssl_certificate_key /etc/nginx/ssl/oa.hyinda.com/server.key; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-CHACHA20-POLY1305; + ssl_prefer_server_ciphers on; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 10m; + + client_max_body_size 20m; + + location / { + limit_req zone=oa_general burst=20 nodelay; + proxy_pass http://hyb_oa_upstream; + proxy_http_version 1.1; + proxy_set_header Host $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 https; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port 443; + proxy_read_timeout 300s; + proxy_send_timeout 300s; + } + + location /metrics { + allow 127.0.0.1; + deny all; + proxy_pass http://hyb_oa_upstream; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-Proto https; + } + + add_header X-Content-Type-Options "nosniff" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + add_header X-XSS-Protection "1; mode=block" always; + # add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; +} diff --git a/deploy/trialrun/nginx.conf.example b/deploy/trialrun/nginx.conf.example index 3946ed21..7120d858 100644 --- a/deploy/trialrun/nginx.conf.example +++ b/deploy/trialrun/nginx.conf.example @@ -1,14 +1,12 @@ # hyb-oa 反向代理示例(Nginx) # -# 说明: -# - 适用于单机试运行:Nginx(443) -> hyb-oa server(3000) -# - server 内已启用 `trustProxy: true`,会读取 X-Forwarded-*。 +# - 快速试运行:可先用本文件的 HTTP 版本 +# - 正式公网:建议直接使用 deploy/trialrun/nginx-hyb-oa.conf # server { listen 80; server_name oa.example.com; - # 试运行可以先走 http;生产建议配 TLS 并把 COOKIE_SECURE=true location / { proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; @@ -17,6 +15,6 @@ server { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; } } - diff --git a/deploy/trialrun/setup-server.sh b/deploy/trialrun/setup-server.sh new file mode 100755 index 00000000..ed56dd50 --- /dev/null +++ b/deploy/trialrun/setup-server.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# ============================================================ +# hyb-oa - 同机部署服务器初始化脚本(在服务器上执行) +# 适用:官网 hyb-web 已在同机运行,现新增 OA 子域名 +# ============================================================ +set -euo pipefail + +DEPLOY_USER="${DEPLOY_USER:-deploy}" +REMOTE_DIR="${REMOTE_DIR:-/opt/hyb-oa-trialrun}" +OA_HOST="${OA_HOST:-oa.hyinda.com}" +NGINX_SITE_NAME="${NGINX_SITE_NAME:-hyb-oa}" + +need_cmd() { + command -v "$1" >/dev/null 2>&1 || { + echo "[ERROR] 缺少命令: $1" >&2 + exit 1 + } +} + +if [[ "$(id -u)" -ne 0 ]]; then + echo "[ERROR] 请以 root 执行该脚本" >&2 + exit 1 +fi + +echo ">>> 1. 安装 Docker / Compose 插件 / rsync" +export DEBIAN_FRONTEND=noninteractive +apt-get update -y +apt-get install -y docker.io rsync + +if apt-cache show docker-compose-plugin >/dev/null 2>&1; then + apt-get install -y docker-compose-plugin +elif apt-cache show docker-compose-v2 >/dev/null 2>&1; then + apt-get install -y docker-compose-v2 +elif apt-cache show docker-compose >/dev/null 2>&1; then + apt-get install -y docker-compose +else + echo "[WARN] 未在当前软件源找到 Compose 插件/包,请后续手工安装 Docker Compose。" >&2 +fi +systemctl enable docker +systemctl start docker + +echo ">>> 2. 让 ${DEPLOY_USER} 可运行 Docker" +id "${DEPLOY_USER}" >/dev/null 2>&1 || { + echo "[ERROR] 用户 ${DEPLOY_USER} 不存在,请先按官网方式创建 deploy 用户" >&2 + exit 1 +} +usermod -aG docker "${DEPLOY_USER}" + +echo ">>> 3. 创建 OA 目录" +mkdir -p "${REMOTE_DIR}/deploy/trialrun/data/postgres" +mkdir -p "${REMOTE_DIR}/deploy/trialrun/data/storage" +mkdir -p "${REMOTE_DIR}/deploy/trialrun/data/minio" +mkdir -p "/etc/nginx/ssl/${OA_HOST}" +chown -R "${DEPLOY_USER}:${DEPLOY_USER}" "${REMOTE_DIR}" + +echo ">>> 4. 初始化完成,后续手工操作:" +echo " 1) 把 OA Nginx 配置放到 /etc/nginx/sites-available/${NGINX_SITE_NAME}" +echo " 2) ln -sf /etc/nginx/sites-available/${NGINX_SITE_NAME} /etc/nginx/sites-enabled/${NGINX_SITE_NAME}" +echo " 3) 上传证书到 /etc/nginx/ssl/${OA_HOST}/server.crt 和 server.key" +echo " 4) 在 ${REMOTE_DIR}/deploy/trialrun/.env 写入 OA 环境变量" +echo " 5) 切换到 deploy 用户后,从本地执行 deploy/trialrun/deploy.sh" +echo " 6) 执行 nginx -t && systemctl reload nginx"