增加腾讯云生产 Docker 部署编排与一键脚本
- 提供 API/Web 多阶段镜像、Nginx 反代与 WS/文件路径 - docker-compose.prod 含 Postgres、MinIO、API、前端 - deploy.sh 生成密钥、构建启动、可选种子与备份脚本
This commit is contained in:
@@ -107,6 +107,21 @@ kefu_sys/
|
||||
|
||||
---
|
||||
|
||||
## 生产部署(腾讯云 Docker)
|
||||
|
||||
单机一键部署(Nginx + API + PostgreSQL + MinIO):
|
||||
|
||||
```bash
|
||||
# 服务器安装 Docker 后
|
||||
chmod +x deploy/scripts/*.sh
|
||||
./deploy/scripts/deploy.sh # 首次会生成 deploy/.env.prod,请改公网地址
|
||||
./deploy/scripts/deploy.sh --seed # 可选:导入演示账号
|
||||
```
|
||||
|
||||
说明见 **[deploy/README.md](deploy/README.md)**(安全组、HTTPS、COS 替换 MinIO、备份升级)。
|
||||
|
||||
---
|
||||
|
||||
## 本地开发
|
||||
|
||||
### 环境要求
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
# 生产部署(Docker / 腾讯云 CVM)
|
||||
|
||||
单机 Docker Compose 方案:`Nginx + API + PostgreSQL + MinIO`,适合腾讯云轻量/ CVM。
|
||||
|
||||
## 架构
|
||||
|
||||
```
|
||||
Internet ──:80──▶ web(nginx)
|
||||
├── / 前端 SPA
|
||||
├── /api/* → api:8080(含 WebSocket)
|
||||
├── /health → api
|
||||
└── /files/* → minio:9000/kefu(图片公共读)
|
||||
```
|
||||
|
||||
## 服务器准备(腾讯云)
|
||||
|
||||
1. 购买 **CVM / 轻量应用服务器**(建议 2 核 4G 起,系统 Ubuntu 22.04)
|
||||
2. **安全组**放行:`22`、`80`(有证书再放 `443`)
|
||||
3. 安装 Docker:
|
||||
|
||||
```bash
|
||||
# Ubuntu 示例
|
||||
curl -fsSL https://get.docker.com | sh
|
||||
sudo usermod -aG docker "$USER"
|
||||
# 重新登录后 docker 无需 sudo
|
||||
docker compose version
|
||||
```
|
||||
|
||||
4. 上传代码到服务器,例如:
|
||||
|
||||
```bash
|
||||
git clone <你的仓库地址> kefu_sys
|
||||
cd kefu_sys
|
||||
```
|
||||
|
||||
## 一键部署
|
||||
|
||||
```bash
|
||||
chmod +x deploy/scripts/*.sh
|
||||
|
||||
# 首次会生成 deploy/.env.prod 并提示你改域名/公网地址
|
||||
./deploy/scripts/deploy.sh
|
||||
|
||||
# 需要演示账号时(kefu_admin / kefu_admin123 等)
|
||||
./deploy/scripts/deploy.sh --seed
|
||||
```
|
||||
|
||||
### 必改配置(`deploy/.env.prod`)
|
||||
|
||||
| 变量 | 示例 | 说明 |
|
||||
|------|------|------|
|
||||
| `APP_BASE_URL` | `http://1.2.3.4` 或 `https://kefu.xx.com` | 浏览器访问根地址 |
|
||||
| `STORAGE_PUBLIC_BASE_URL` | `http://1.2.3.4/files` | **必须**与 Nginx `/files` 一致 |
|
||||
| `HTTP_PORT` | `80` | 宿主机端口 |
|
||||
| `DB_PASSWORD` / `JWT_SECRET` / `STORAGE_*` | 强随机 | 脚本可自动生成密钥 |
|
||||
|
||||
脚本会校验占位符是否改掉。
|
||||
|
||||
### 常用命令
|
||||
|
||||
```bash
|
||||
./deploy/scripts/deploy.sh --status # 状态
|
||||
./deploy/scripts/deploy.sh --down # 停容器(保留卷)
|
||||
./deploy/scripts/deploy.sh --seed-only # 仅跑种子
|
||||
./deploy/scripts/backup-db.sh # 备份数据库到 ./backups
|
||||
|
||||
docker compose -f docker-compose.prod.yml --env-file deploy/.env.prod logs -f api
|
||||
docker compose -f docker-compose.prod.yml --env-file deploy/.env.prod logs -f web
|
||||
```
|
||||
|
||||
## HTTPS 建议(腾讯云)
|
||||
|
||||
任选其一:
|
||||
|
||||
1. **CLB / CDN** 终结 HTTPS,回源 CVM `80`(最简单)
|
||||
2. 本机 **Caddy / Nginx + Let’s Encrypt** 反代到本 compose 的 `80`
|
||||
3. 把 `web` 容器映射改到 `8080`,宿主机 Nginx 做 443 → 8080
|
||||
|
||||
配置 HTTPS 后,请同步把 `APP_BASE_URL`、`STORAGE_PUBLIC_BASE_URL` 改成 `https://…`。
|
||||
|
||||
## 使用腾讯云 COS 替代 MinIO(可选)
|
||||
|
||||
1. 创建 COS 桶,开启公共读或使用 CDN 域名
|
||||
2. 在 `.env.prod` 配置 S3 兼容参数(见 `.env.prod.example` 注释)
|
||||
3. 从 `docker-compose.prod.yml` 中去掉 `minio` / `minio-init` 依赖,并修改 Nginx,去掉 `/files` 反代(图片直链 COS)
|
||||
|
||||
当前一键脚本默认 **内置 MinIO**,零额外云产品即可跑通。
|
||||
|
||||
## 默认种子账号(仅 --seed 后)
|
||||
|
||||
| 角色 | 用户名 | 密码 |
|
||||
|------|--------|------|
|
||||
| 平台管理员 | `platform_admin` | `kefu_admin123` |
|
||||
| 租户管理员 | `kefu_admin` | `kefu_admin123` |
|
||||
| 坐席 | `agent1` 等 | `kefu_admin123` |
|
||||
|
||||
**上线后立即修改密码或关闭 seed 账号。**
|
||||
|
||||
## 目录说明
|
||||
|
||||
| 路径 | 说明 |
|
||||
|------|------|
|
||||
| `docker-compose.prod.yml` | 生产编排 |
|
||||
| `deploy/docker/Dockerfile.api` | Go API 镜像 |
|
||||
| `deploy/docker/Dockerfile.web` | 前端构建 + Nginx |
|
||||
| `deploy/docker/nginx.conf` | 反代与 SPA |
|
||||
| `deploy/.env.prod.example` | 环境变量模板 |
|
||||
| `deploy/scripts/deploy.sh` | 一键部署 |
|
||||
| `deploy/scripts/backup-db.sh` | 数据库备份 |
|
||||
|
||||
## 故障排查
|
||||
|
||||
| 现象 | 处理 |
|
||||
|------|------|
|
||||
| 页面 502 | `docker compose … logs api`,检查 DB 是否 healthy |
|
||||
| 图片打不开 | 检查 `STORAGE_PUBLIC_BASE_URL` 是否为 `…/files`,MinIO 桶是否 public download |
|
||||
| WebSocket 断 | 确认走同源 `/api/ws`,CLB 需开启 WebSocket/长连接 |
|
||||
| 构建 Go 失败 | 服务器内存不足时加 swap,或本机 build 后 `docker save` 上传 |
|
||||
|
||||
## 升级
|
||||
|
||||
```bash
|
||||
git pull
|
||||
./deploy/scripts/deploy.sh # 重新 build 并滚动启动
|
||||
./deploy/scripts/backup-db.sh # 升级前建议备份
|
||||
```
|
||||
@@ -0,0 +1,44 @@
|
||||
# 客服云 API — 生产镜像(多阶段构建)
|
||||
# build context: 仓库根目录
|
||||
# docker build -f deploy/docker/Dockerfile.api -t kefu-api:latest .
|
||||
|
||||
FROM golang:1.24-alpine AS builder
|
||||
|
||||
# 若 go.mod 要求更高版本,自动下载 toolchain
|
||||
ENV GOTOOLCHAIN=auto
|
||||
ENV CGO_ENABLED=0
|
||||
|
||||
RUN apk add --no-cache git ca-certificates tzdata
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
# 先下依赖,利用缓存
|
||||
COPY server/go.mod server/go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
COPY server/ ./
|
||||
RUN GOOS=linux GOARCH=amd64 \
|
||||
go build -trimpath -ldflags="-s -w" -o /out/server ./cmd/main.go
|
||||
|
||||
# 种子程序(compose profile seed 使用)
|
||||
RUN GOOS=linux GOARCH=amd64 \
|
||||
go build -trimpath -ldflags="-s -w" -o /out/seed ./cmd/seed
|
||||
|
||||
FROM alpine:3.21
|
||||
|
||||
RUN apk add --no-cache ca-certificates tzdata wget \
|
||||
&& adduser -D -H -u 10001 kefu
|
||||
|
||||
ENV TZ=Asia/Shanghai
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=builder /out/server /app/server
|
||||
COPY --from=builder /out/seed /app/seed
|
||||
|
||||
USER kefu
|
||||
EXPOSE 8080
|
||||
|
||||
HEALTHCHECK --interval=15s --timeout=5s --start-period=20s --retries=5 \
|
||||
CMD wget -qO- http://127.0.0.1:8080/health || exit 1
|
||||
|
||||
ENTRYPOINT ["/app/server"]
|
||||
@@ -0,0 +1,25 @@
|
||||
# 客服云前端 — Nginx 托管静态资源并反代 API / 文件
|
||||
# build context: 仓库根目录
|
||||
|
||||
FROM node:22-alpine AS builder
|
||||
|
||||
WORKDIR /web
|
||||
COPY web/package.json web/package-lock.json ./
|
||||
RUN npm ci
|
||||
|
||||
COPY web/ ./
|
||||
RUN npm run build
|
||||
|
||||
FROM nginx:1.27-alpine
|
||||
|
||||
RUN apk add --no-cache tzdata \
|
||||
&& cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
|
||||
&& echo Asia/Shanghai > /etc/timezone
|
||||
|
||||
COPY deploy/docker/nginx.conf /etc/nginx/conf.d/default.conf
|
||||
COPY --from=builder /web/dist /usr/share/nginx/html
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
HEALTHCHECK --interval=15s --timeout=5s --start-period=10s --retries=5 \
|
||||
CMD wget -qO- http://127.0.0.1/healthz || exit 1
|
||||
@@ -0,0 +1,89 @@
|
||||
# 生产 Nginx:SPA + API/WebSocket 反代 + 对象存储公共读路径
|
||||
# 上游服务名与 docker-compose.prod.yml 中 service 名一致
|
||||
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
|
||||
upstream kefu_api {
|
||||
server api:8080;
|
||||
keepalive 32;
|
||||
}
|
||||
|
||||
upstream kefu_minio {
|
||||
server minio:9000;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
client_max_body_size 20m;
|
||||
# 上传/长连接
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
|
||||
# 健康探针(不经 API)
|
||||
location = /healthz {
|
||||
access_log off;
|
||||
add_header Content-Type text/plain;
|
||||
return 200 'ok';
|
||||
}
|
||||
|
||||
# 后端健康检查
|
||||
location = /health {
|
||||
proxy_pass http://kefu_api/health;
|
||||
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 $scheme;
|
||||
}
|
||||
|
||||
# API + WebSocket(/api/ws、/api/widget/ws)
|
||||
location /api/ {
|
||||
proxy_pass http://kefu_api/api/;
|
||||
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 $scheme;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
proxy_buffering off;
|
||||
}
|
||||
|
||||
# 图片公共访问:浏览器访问 /files/* → MinIO bucket kefu/*
|
||||
# 须与 STORAGE_PUBLIC_BASE_URL=https://你的域名/files 一致
|
||||
location /files/ {
|
||||
proxy_pass http://kefu_minio/kefu/;
|
||||
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 $scheme;
|
||||
# 允许跨域拉取图片(嵌入站点)
|
||||
add_header Access-Control-Allow-Origin * always;
|
||||
add_header Access-Control-Allow-Methods "GET, HEAD, OPTIONS" always;
|
||||
if ($request_method = OPTIONS) {
|
||||
return 204;
|
||||
}
|
||||
expires 7d;
|
||||
add_header Cache-Control "public" always;
|
||||
}
|
||||
|
||||
# 前端静态资源
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# 构建产物长缓存
|
||||
location /assets/ {
|
||||
root /usr/share/nginx/html;
|
||||
expires 30d;
|
||||
add_header Cache-Control "public, immutable";
|
||||
try_files $uri =404;
|
||||
}
|
||||
}
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
# 备份生产 Postgres 数据
|
||||
# 用法:./deploy/scripts/backup-db.sh [输出目录]
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
cd "$ROOT_DIR"
|
||||
|
||||
ENV_FILE="deploy/.env.prod"
|
||||
OUT_DIR="${1:-./backups}"
|
||||
COMPOSE_FILE="docker-compose.prod.yml"
|
||||
|
||||
if [[ ! -f "$ENV_FILE" ]]; then
|
||||
echo "缺少 $ENV_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC1090
|
||||
set -a
|
||||
source <(sed 's/\r$//' "$ENV_FILE" | grep -v '^\s*#' | grep -v '^\s*$' || true)
|
||||
set +a
|
||||
|
||||
mkdir -p "$OUT_DIR"
|
||||
STAMP="$(date +%Y%m%d_%H%M%S)"
|
||||
FILE="${OUT_DIR}/kefu_pg_${STAMP}.sql.gz"
|
||||
|
||||
echo "备份到 $FILE …"
|
||||
docker compose -f "$COMPOSE_FILE" --env-file "$ENV_FILE" exec -T postgres \
|
||||
pg_dump -U "${DB_USER:-kefu}" "${DB_NAME:-kefu_sys}" | gzip > "$FILE"
|
||||
|
||||
echo "完成: $FILE ($(du -h "$FILE" | awk '{print $1}'))"
|
||||
Executable
+252
@@ -0,0 +1,252 @@
|
||||
#!/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-prod_kefu_pgdata kefu-prod_kefu_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 "$@"
|
||||
@@ -0,0 +1,157 @@
|
||||
# 客服云 — 生产 Docker Compose(适合腾讯云 CVM 单机)
|
||||
# 使用:./deploy/scripts/deploy.sh
|
||||
# 或:docker compose -f docker-compose.prod.yml --env-file deploy/.env.prod up -d --build
|
||||
|
||||
name: kefu-prod
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
container_name: kefu-prod-db
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: ${DB_USER:-kefu}
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD:?请在 deploy/.env.prod 设置 DB_PASSWORD}
|
||||
POSTGRES_DB: ${DB_NAME:-kefu_sys}
|
||||
volumes:
|
||||
- kefu_pgdata:/var/lib/postgresql/data
|
||||
networks:
|
||||
- kefu_net
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-kefu} -d ${DB_NAME:-kefu_sys}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 20s
|
||||
|
||||
minio:
|
||||
image: minio/minio:latest
|
||||
container_name: kefu-prod-minio
|
||||
restart: unless-stopped
|
||||
command: server /data --console-address ":9001"
|
||||
environment:
|
||||
MINIO_ROOT_USER: ${STORAGE_ACCESS_KEY:?请设置 STORAGE_ACCESS_KEY}
|
||||
MINIO_ROOT_PASSWORD: ${STORAGE_SECRET_KEY:?请设置 STORAGE_SECRET_KEY}
|
||||
volumes:
|
||||
- kefu_miniodata:/data
|
||||
networks:
|
||||
- kefu_net
|
||||
# 官方镜像极简,无 curl/bash;由 minio-init 重试建桶保证就绪
|
||||
|
||||
minio-init:
|
||||
image: minio/mc:latest
|
||||
container_name: kefu-prod-minio-init
|
||||
depends_on:
|
||||
minio:
|
||||
condition: service_started
|
||||
restart: "no"
|
||||
networks:
|
||||
- kefu_net
|
||||
entrypoint: >
|
||||
/bin/sh -c "
|
||||
set -e;
|
||||
i=0;
|
||||
until mc alias set local http://minio:9000 \"$$MINIO_ROOT_USER\" \"$$MINIO_ROOT_PASSWORD\" 2>/dev/null; do
|
||||
i=$$((i+1));
|
||||
if [ $$i -gt 30 ]; then echo 'minio not ready'; exit 1; fi;
|
||||
echo waiting minio...;
|
||||
sleep 2;
|
||||
done;
|
||||
mc mb -p local/$${STORAGE_BUCKET:-kefu} || true;
|
||||
mc anonymous set download local/$${STORAGE_BUCKET:-kefu};
|
||||
echo 'MinIO bucket ready';
|
||||
"
|
||||
environment:
|
||||
MINIO_ROOT_USER: ${STORAGE_ACCESS_KEY}
|
||||
MINIO_ROOT_PASSWORD: ${STORAGE_SECRET_KEY}
|
||||
STORAGE_BUCKET: ${STORAGE_BUCKET:-kefu}
|
||||
|
||||
api:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: deploy/docker/Dockerfile.api
|
||||
image: kefu-api:${IMAGE_TAG:-latest}
|
||||
container_name: kefu-prod-api
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
minio-init:
|
||||
condition: service_completed_successfully
|
||||
environment:
|
||||
SERVER_PORT: "8080"
|
||||
GIN_MODE: ${GIN_MODE:-release}
|
||||
DB_HOST: postgres
|
||||
DB_PORT: "5432"
|
||||
DB_USER: ${DB_USER:-kefu}
|
||||
DB_PASSWORD: ${DB_PASSWORD}
|
||||
DB_NAME: ${DB_NAME:-kefu_sys}
|
||||
DB_SSLMODE: ${DB_SSLMODE:-disable}
|
||||
JWT_SECRET: ${JWT_SECRET:?请设置 JWT_SECRET}
|
||||
STORAGE_ENDPOINT: minio:9000
|
||||
STORAGE_ACCESS_KEY: ${STORAGE_ACCESS_KEY}
|
||||
STORAGE_SECRET_KEY: ${STORAGE_SECRET_KEY}
|
||||
STORAGE_BUCKET: ${STORAGE_BUCKET:-kefu}
|
||||
STORAGE_USE_SSL: "false"
|
||||
STORAGE_REGION: ${STORAGE_REGION:-us-east-1}
|
||||
# 浏览器访问图片的公网前缀(经 Nginx /files 反代)
|
||||
STORAGE_PUBLIC_BASE_URL: ${STORAGE_PUBLIC_BASE_URL:?请设置 STORAGE_PUBLIC_BASE_URL,如 https://你的域名/files}
|
||||
STORAGE_MAX_UPLOAD_MB: ${STORAGE_MAX_UPLOAD_MB:-10}
|
||||
STORAGE_MAX_IMAGE_EDGE: ${STORAGE_MAX_IMAGE_EDGE:-1920}
|
||||
STORAGE_WEBP_QUALITY: ${STORAGE_WEBP_QUALITY:-80}
|
||||
networks:
|
||||
- kefu_net
|
||||
# 不对外暴露 8080,统一走 nginx
|
||||
|
||||
web:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: deploy/docker/Dockerfile.web
|
||||
image: kefu-web:${IMAGE_TAG:-latest}
|
||||
container_name: kefu-prod-web
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
api:
|
||||
condition: service_healthy
|
||||
minio:
|
||||
condition: service_started
|
||||
ports:
|
||||
- "${HTTP_PORT:-80}:80"
|
||||
networks:
|
||||
- kefu_net
|
||||
|
||||
# 可选:导入演示数据(profile seed)
|
||||
# docker compose -f docker-compose.prod.yml --env-file deploy/.env.prod --profile seed run --rm seed
|
||||
seed:
|
||||
image: kefu-api:${IMAGE_TAG:-latest}
|
||||
container_name: kefu-prod-seed
|
||||
profiles: ["seed"]
|
||||
depends_on:
|
||||
api:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
DB_HOST: postgres
|
||||
DB_PORT: "5432"
|
||||
DB_USER: ${DB_USER:-kefu}
|
||||
DB_PASSWORD: ${DB_PASSWORD}
|
||||
DB_NAME: ${DB_NAME:-kefu_sys}
|
||||
DB_SSLMODE: ${DB_SSLMODE:-disable}
|
||||
JWT_SECRET: ${JWT_SECRET}
|
||||
STORAGE_ENDPOINT: minio:9000
|
||||
STORAGE_ACCESS_KEY: ${STORAGE_ACCESS_KEY}
|
||||
STORAGE_SECRET_KEY: ${STORAGE_SECRET_KEY}
|
||||
STORAGE_BUCKET: ${STORAGE_BUCKET:-kefu}
|
||||
STORAGE_USE_SSL: "false"
|
||||
STORAGE_PUBLIC_BASE_URL: ${STORAGE_PUBLIC_BASE_URL}
|
||||
entrypoint: ["/app/seed"]
|
||||
networks:
|
||||
- kefu_net
|
||||
restart: "no"
|
||||
|
||||
networks:
|
||||
kefu_net:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
kefu_pgdata:
|
||||
kefu_miniodata:
|
||||
Reference in New Issue
Block a user