feat(web): 部署方式由 Docker 改为 Linux 原生站点(nginx + systemd)
需求变更:Web 服务端不再使用 Docker,改为直接运行在 Linux 上的网站。 - 移除 deploy/docker-compose.yml、web-server/Dockerfile、 web-server/admin/Dockerfile、web-server/.dockerignore - 新增 deploy/install.sh:建服务用户与目录 → 装运行期依赖 → 迁移 → 注册 systemd 服务 → 配置 nginx 站点 → 健康检查,一键完成 - 新增 deploy/systemd/opendance-api.service:ProtectSystem=strict 等 安全加固,ReadWritePaths 限定为上传目录 - 新增 deploy/nginx/opendance.conf:同站点托管管理端静态资源 + 反代 /api /health /static,client_max_body_size 512m,SPA 回退 - 新增 deploy/uninstall.sh:默认保留数据,--purge 才删除 - 新增配置项 HOST(AppConfig.bindHost):原生部署下 Node 只监听 127.0.0.1,不直接对外暴露,实测外部 IP 不可达 - 同步更新 contracts/rest-api.yaml、DSH.md、project.yaml、README.txt、 tasks/008、tasks/010 与发布打包脚本
This commit is contained in:
@@ -1,11 +0,0 @@
|
||||
node_modules
|
||||
dist
|
||||
coverage
|
||||
storage
|
||||
data
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
*.log
|
||||
test
|
||||
admin
|
||||
@@ -1,50 +0,0 @@
|
||||
# OpenDance Studio Web 服务端 —— 生产镜像
|
||||
#
|
||||
# 构建上下文为 web-server/(与 contracts/database.md、deploy/docker-compose.yml 一致)。
|
||||
# 镜像内**不包含 MySQL**:数据库为外部现有实例,通过环境变量注入连接信息。
|
||||
|
||||
# ---------- 构建阶段 ----------
|
||||
FROM node:22-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# 先装依赖,最大化利用层缓存
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci --no-audit --no-fund
|
||||
|
||||
# 编译 TypeScript
|
||||
COPY tsconfig.json tsconfig.build.json nest-cli.json ./
|
||||
COPY src ./src
|
||||
RUN npm run build
|
||||
|
||||
# 仅保留运行时依赖
|
||||
RUN npm prune --omit=dev
|
||||
|
||||
# ---------- 运行阶段 ----------
|
||||
FROM node:22-alpine AS runtime
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV=production \
|
||||
PORT=3000 \
|
||||
STORAGE_DIR=/data/storage
|
||||
|
||||
# 迁移需要编译后的 data-source.js + typeorm CLI(均为运行时依赖)
|
||||
COPY --from=builder /app/node_modules ./node_modules
|
||||
COPY --from=builder /app/dist ./dist
|
||||
COPY --from=builder /app/package.json ./package.json
|
||||
COPY scripts ./scripts
|
||||
COPY .env.example ./.env.example
|
||||
|
||||
# 非 root 运行;上传目录为卷挂载点
|
||||
RUN mkdir -p /data/storage && chown -R node:node /data /app
|
||||
USER node
|
||||
|
||||
VOLUME ["/data/storage"]
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
|
||||
CMD node -e "fetch('http://127.0.0.1:'+(process.env.PORT||3000)+'/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
|
||||
|
||||
CMD ["node", "dist/main.js"]
|
||||
@@ -1,17 +0,0 @@
|
||||
# ---- 构建阶段 ----
|
||||
FROM node:22-alpine AS build
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json package-lock.json* ./
|
||||
RUN npm ci || npm install
|
||||
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
# ---- 运行阶段 ----
|
||||
FROM nginx:alpine
|
||||
COPY --from=build /app/dist /usr/share/nginx/html
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
EXPOSE 80
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
@@ -33,6 +33,14 @@ export class AppConfig {
|
||||
return this.config.get<number>('port') ?? 3000;
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听地址。原生部署(nginx 前置)时设为 127.0.0.1,
|
||||
* 仅允许本机反代访问,不直接暴露 Node 进程。
|
||||
*/
|
||||
get bindHost(): string {
|
||||
return this.config.get<string>('bindHost') ?? '0.0.0.0';
|
||||
}
|
||||
|
||||
get storageDir(): string {
|
||||
return this.config.get<string>('storageDir') ?? resolve(process.cwd(), 'storage');
|
||||
}
|
||||
@@ -75,6 +83,7 @@ function bool(value: string | undefined, fallback = false): boolean {
|
||||
export const configuration = () => ({
|
||||
nodeEnv: process.env.NODE_ENV ?? 'development',
|
||||
port: Number(process.env.PORT ?? 3000),
|
||||
bindHost: process.env.HOST ?? '0.0.0.0',
|
||||
storageDir: process.env.STORAGE_DIR ?? resolve(process.cwd(), 'storage'),
|
||||
uploadMaxBytes: Number(process.env.UPLOAD_MAX_BYTES ?? 512 * 1024 * 1024),
|
||||
assimpBin: process.env.ASSIMP_BIN ?? 'assimp',
|
||||
|
||||
@@ -54,10 +54,10 @@ async function bootstrap(): Promise<void> {
|
||||
app.enableShutdownHooks();
|
||||
|
||||
const db = config.db;
|
||||
await app.listen(config.port, '0.0.0.0');
|
||||
await app.listen(config.port, config.bindHost);
|
||||
|
||||
logger.log(`OpenDance Studio Web 服务端已启动:http://0.0.0.0:${config.port}`);
|
||||
logger.log(`健康检查:http://0.0.0.0:${config.port}/health`);
|
||||
logger.log(`OpenDance Studio Web 服务端已启动:http://${config.bindHost}:${config.port}`);
|
||||
logger.log(`健康检查:http://${config.bindHost}:${config.port}/health`);
|
||||
logger.log(`数据库:${db.host}:${db.port}/${db.name}(现有实例,本服务不管理 MySQL)`);
|
||||
logger.log(`文件存储:${storageDir}`);
|
||||
logger.log(`契约版本:${JSON.stringify(config.contractVersions)}`);
|
||||
|
||||
Reference in New Issue
Block a user