feat: bootstrap commercial AI drama platform

This commit is contained in:
xz
2026-08-24 10:24:34 +08:00
commit ffb27d845b
100 changed files with 35314 additions and 0 deletions
+23
View File
@@ -0,0 +1,23 @@
COMPOSE_PROJECT_NAME=ai-drama-platform
PUBLIC_ORIGIN=http://localhost
HTTP_PORT=80
VITE_API_BASE=
# Application secrets. Generate long random values; do not commit the real file.
AI_DRAMA_SESSION_SECRET=replace-with-a-long-random-secret
AI_DRAMA_OIDC_STORAGE_KEY=replace-with-a-different-long-random-secret
AI_DRAMA_MFA_ENCRYPTION_KEY=replace-with-32-byte-base64-or-hex-key
# Optional SAML IdP certificate. It can be PEM text or base64 certificate content.
AI_DRAMA_SAML_IDP_CERT=
# Provisioned provider contract. Current business runtime remains SQLite.
POSTGRES_DB=ai_drama
POSTGRES_USER=ai_drama
POSTGRES_PASSWORD=replace-with-postgres-password
REDIS_PASSWORD=replace-with-redis-password
MINIO_ROOT_USER=ai-drama-admin
MINIO_ROOT_PASSWORD=replace-with-minio-password
MINIO_BUCKET=ai-drama
MINIO_API_PORT=9000
MINIO_CONSOLE_PORT=9001
+18
View File
@@ -0,0 +1,18 @@
# 私有商业部署 Profile
`docker-compose.production.yml` 提供一套可审计的私有部署边界:前端 Nginx、API、独立 Worker、PostgreSQL、Redis 和 MinIO/S3-compatible 对象存储。
当前代码的业务数据库仍由 `server/db.mjs` 使用 Node 24 SQLite 驱动,生产 compose 会把 PostgreSQL、Redis 和对象存储启动起来并注入连接契约,但不会把 SQLite 伪装成 PostgreSQL 运行时。完成 PostgreSQL/Redis/对象存储 provider 迁移后,才可以把对应 `PLATFORM_*` 变量切换为业务真源。
## 启动
```bash
cd /Users/xz/Documents/daima/ai短剧/ai-drama-platform
cp deploy/.env.production.example .env.production
# 编辑 .env.production,替换全部 replace-with-* 值
docker compose --env-file .env.production -f deploy/docker-compose.production.yml up -d --build
```
默认入口是 `http://localhost/`。第一次上线前应额外完成 HTTPS 反向代理、备份策略、把 MinIO 镜像替换为经过验证的固定 digest、容器镜像签名、日志收集、PostgreSQL/Redis/对象存储 provider 迁移,以及自有模型 Runner 的网络隔离和审计批准。
本地 SQLite 运行时提供系统管理员快照接口:`POST /api/system/backups`。它只创建 `data/backups/` 下的 SQLite 文件并写审计,不替代生产环境的 PostgreSQL、对象存储和异地备份策略;部署上线前应验证快照可读性,并配置独立备份保留和恢复演练。
+13
View File
@@ -0,0 +1,13 @@
FROM node:24-bookworm-slim
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
RUN mkdir -p /app/data /app/storage /app/exports
EXPOSE 8787
CMD ["node", "server/local-api.mjs"]
+132
View File
@@ -0,0 +1,132 @@
name: ai-drama-platform
# Deployment boundary for a commercial private installation.
# The current application runtime still uses SQLite through AI_DRAMA_DB_PATH.
# PostgreSQL, Redis and S3-compatible storage are provisioned here as the
# target provider contract; switching business runtime adapters is a separate
# implementation step and must not be inferred from this compose file.
services:
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_DB: ${POSTGRES_DB:-ai_drama}
POSTGRES_USER: ${POSTGRES_USER:-ai_drama}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 10
redis:
image: redis:7-alpine
restart: unless-stopped
command: ["redis-server", "--appendonly", "yes", "--requirepass", "${REDIS_PASSWORD:?set REDIS_PASSWORD}"]
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
interval: 10s
timeout: 5s
retries: 10
object-storage:
image: minio/minio:latest
restart: unless-stopped
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER:?set MINIO_ROOT_USER}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:?set MINIO_ROOT_PASSWORD}
volumes:
- object-storage-data:/data
ports:
- "${MINIO_API_PORT:-9000}:9000"
- "${MINIO_CONSOLE_PORT:-9001}:9001"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 10s
timeout: 5s
retries: 10
api:
build:
context: ..
dockerfile: deploy/api.Dockerfile
restart: unless-stopped
environment:
AI_DRAMA_API_PORT: 8787
AI_DRAMA_API_ORIGIN: ${PUBLIC_ORIGIN:-http://localhost}
AI_DRAMA_FRONTEND_ORIGIN: ${PUBLIC_ORIGIN:-http://localhost}
AI_DRAMA_DB_PATH: /app/data/platform.sqlite
AI_DRAMA_ALLOW_DEV_CONTEXT: "0"
AI_DRAMA_SESSION_SECRET: ${AI_DRAMA_SESSION_SECRET:?set AI_DRAMA_SESSION_SECRET}
AI_DRAMA_OIDC_STORAGE_KEY: ${AI_DRAMA_OIDC_STORAGE_KEY:?set AI_DRAMA_OIDC_STORAGE_KEY}
AI_DRAMA_MFA_ENCRYPTION_KEY: ${AI_DRAMA_MFA_ENCRYPTION_KEY:?set AI_DRAMA_MFA_ENCRYPTION_KEY}
AI_DRAMA_SAML_IDP_CERT: ${AI_DRAMA_SAML_IDP_CERT:-}
PLATFORM_POSTGRES_URL: postgresql://${POSTGRES_USER:-ai_drama}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-ai_drama}
PLATFORM_REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379/0
PLATFORM_OBJECT_STORAGE_ENDPOINT: http://object-storage:9000
PLATFORM_OBJECT_STORAGE_BUCKET: ${MINIO_BUCKET:-ai-drama}
PLATFORM_OBJECT_STORAGE_ACCESS_KEY: ${MINIO_ROOT_USER}
PLATFORM_OBJECT_STORAGE_SECRET_KEY: ${MINIO_ROOT_PASSWORD}
volumes:
- platform-data:/app/data
- platform-storage:/app/storage
- platform-exports:/app/exports
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
object-storage:
condition: service_healthy
expose:
- "8787"
healthcheck:
test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:8787/api/health').then(r => { if (!r.ok) process.exit(1) }).catch(() => process.exit(1))"]
interval: 10s
timeout: 5s
retries: 12
worker:
build:
context: ..
dockerfile: deploy/api.Dockerfile
restart: unless-stopped
command: ["npm", "run", "worker"]
environment:
AI_DRAMA_DB_PATH: /app/data/platform.sqlite
AI_DRAMA_SESSION_SECRET: ${AI_DRAMA_SESSION_SECRET:?set AI_DRAMA_SESSION_SECRET}
PLATFORM_REDIS_URL: redis://:${REDIS_PASSWORD}@redis:6379/0
volumes:
- platform-data:/app/data
- platform-storage:/app/storage
- platform-exports:/app/exports
depends_on:
api:
condition: service_healthy
frontend:
build:
context: ..
dockerfile: deploy/frontend.Dockerfile
args:
VITE_API_BASE: ${VITE_API_BASE:-}
restart: unless-stopped
ports:
- "${HTTP_PORT:-80}:80"
depends_on:
api:
condition: service_healthy
volumes:
postgres-data:
redis-data:
object-storage-data:
platform-data:
platform-storage:
platform-exports:
+15
View File
@@ -0,0 +1,15 @@
FROM node:24-bookworm-slim AS build
WORKDIR /app
ARG VITE_API_BASE=
ENV VITE_API_BASE=$VITE_API_BASE
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:1.27-alpine
COPY deploy/nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
+31
View File
@@ -0,0 +1,31 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
location /api/ {
proxy_pass http://api:8787;
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;
client_max_body_size 2g;
}
location /scim/ {
proxy_pass http://api:8787;
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;
client_max_body_size 20m;
}
location / {
try_files $uri $uri/ /index.html;
}
}