diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..983b9c0
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,16 @@
+node_modules
+build
+dist
+logs
+uploads
+.git
+.gitignore
+*.md
+LICENSE
+NOTICES.txt
+electron-builder.yml
+backup
+env
+docs
+*.log
+.env*
diff --git a/README.md b/README.md
index 802a9ab..1543293 100644
--- a/README.md
+++ b/README.md
@@ -375,11 +375,16 @@ pm2 monit # 监控面板
~~交流群 4~~
+
~~交流群 5~~
-交流群 6:
+~~交流群 6~~
-
+~~交流群 7~~
+
+交流群 8:
+
+
使用微信扫码添加,二维码过期可提交 Issues 提醒更新
--- @@ -400,11 +405,11 @@ Toonflow 基于 AGPL-3.0 协议开源发布,许可证详情:https://www.gnu. --- -# ⭐️ 星标历史 + # 🙏 致谢 diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..bd7c07c --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,120 @@ +# 构建阶段 +FROM node:24-alpine AS builder + +WORKDIR /app + +# 定义构建参数 +ARG GIT=github +ARG TAG="" +ARG BRANCH="" + +# 安装 git +RUN apk add --no-cache git + +RUN npm config set registry https://registry.npmmirror.com/ && \ + yarn config set registry https://registry.npmmirror.com/ + +# 根据参数选择仓库源,支持 TAG / BRANCH 切换 +# 优先级: TAG > BRANCH > 最新 tag > 默认分支 +RUN if [ "$GIT" = "gitee" ]; then \ + REPO_URL="https://gitee.com/HBAI-Ltd/Toonflow-app.git"; \ + else \ + REPO_URL="https://github.com/HBAI-Ltd/Toonflow-app.git"; \ + fi && \ + echo "Cloning from: $REPO_URL" && \ + git clone "$REPO_URL" . && \ + if [ -n "$TAG" ]; then \ + echo "Checking out specified tag: $TAG" && \ + git checkout "$TAG"; \ + elif [ -n "$BRANCH" ]; then \ + echo "Checking out specified branch: $BRANCH" && \ + git checkout "$BRANCH"; \ + else \ + LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || git tag --sort=-v:refname | head -n 1) && \ + if [ -n "$LATEST_TAG" ]; then \ + echo "Checking out latest tag: $LATEST_TAG" && \ + git checkout "$LATEST_TAG"; \ + else \ + echo "No tags found, using default branch"; \ + fi; \ + fi && \ + echo "Current version:" && git describe --tags --always + +RUN yarn install --frozen-lockfile + +RUN yarn build + +# 生产阶段 +FROM node:24-alpine + +WORKDIR /app + +# 安装 nginx 和 supervisor +RUN apk add --no-cache nginx supervisor && \ + mkdir -p /var/lib/nginx/logs /var/log/nginx && \ + npm config set registry https://registry.npmmirror.com/ && \ + yarn config set registry https://registry.npmmirror.com/ && \ + npm install -g pm2 + +# 复制后端文件 +COPY --from=builder /app/build ./build +COPY --from=builder /app/package.json ./ +COPY --from=builder /app/yarn.lock ./ + +# 复制静态页面到 nginx 目录 +COPY --from=builder /app/scripts/web /usr/share/nginx/html + +# 只安装生产依赖 +RUN yarn install --frozen-lockfile --production + +# 配置 nginx +RUN cat > /etc/nginx/http.d/default.conf << 'EOF' +server { + listen 80; + location / { + root /usr/share/nginx/html; + index index.html; + try_files $uri $uri/ /index.html; + } +} +EOF + +# 配置 nginx 主配置,日志输出到 stderr/stdout +RUN sed -i 's|error_log /var/log/nginx/error.log warn;|error_log /dev/stderr warn;|g' /etc/nginx/nginx.conf || true && \ + sed -i 's|access_log /var/log/nginx/access.log main;|access_log /dev/stdout main;|g' /etc/nginx/nginx.conf || true + +# 配置 supervisor +RUN cat > /etc/supervisord.conf << 'EOF' +[supervisord] +nodaemon=true +logfile=/var/log/supervisord.log +pidfile=/var/run/supervisord.pid + +[program:nginx] +command=nginx -g "daemon off;" +autostart=true +autorestart=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +[program:app] +command=pm2-runtime start build/app.js --name app +directory=/app +autostart=true +autorestart=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +environment=NODE_ENV=prod +EOF + +ENV NODE_ENV=prod + +EXPOSE 80 +EXPOSE 60000 + +# 启动时创建必要目录(防止 volume 挂载覆盖) +CMD sh -c "mkdir -p /var/log/nginx /var/lib/nginx/logs && exec supervisord -c /etc/supervisord.conf" diff --git a/docker/Dockerfile.local b/docker/Dockerfile.local new file mode 100644 index 0000000..3745495 --- /dev/null +++ b/docker/Dockerfile.local @@ -0,0 +1,94 @@ +# 本地构建阶段 - 使用本地源码,不从 git 克隆 +FROM node:24-alpine AS builder + +WORKDIR /app + +RUN npm config set registry https://registry.npmmirror.com/ && \ + yarn config set registry https://registry.npmmirror.com/ + +# 复制依赖文件 +COPY package.json yarn.lock ./ + +RUN yarn install --frozen-lockfile + +# 复制源码 +COPY tsconfig.json ./ +COPY src/ ./src/ +COPY scripts/ ./scripts/ + +RUN yarn build + +# 生产阶段 +FROM node:24-alpine + +WORKDIR /app + +# 安装 nginx 和 supervisor +RUN apk add --no-cache nginx supervisor && \ + mkdir -p /var/lib/nginx/logs /var/log/nginx && \ + npm config set registry https://registry.npmmirror.com/ && \ + yarn config set registry https://registry.npmmirror.com/ && \ + npm install -g pm2 + +# 复制后端文件 +COPY --from=builder /app/build ./build +COPY --from=builder /app/package.json ./ +COPY --from=builder /app/yarn.lock ./ + +# 复制静态页面到 nginx 目录 +COPY --from=builder /app/scripts/web /usr/share/nginx/html + +# 只安装生产依赖 +RUN yarn install --frozen-lockfile --production + +# 配置 nginx +RUN cat > /etc/nginx/http.d/default.conf << 'EOF' +server { + listen 80; + location / { + root /usr/share/nginx/html; + index index.html; + try_files $uri $uri/ /index.html; + } +} +EOF + +# 配置 nginx 主配置,日志输出到 stderr/stdout +RUN sed -i 's|error_log /var/log/nginx/error.log warn;|error_log /dev/stderr warn;|g' /etc/nginx/nginx.conf || true && \ + sed -i 's|access_log /var/log/nginx/access.log main;|access_log /dev/stdout main;|g' /etc/nginx/nginx.conf || true + +# 配置 supervisor +RUN cat > /etc/supervisord.conf << 'EOF' +[supervisord] +nodaemon=true +logfile=/var/log/supervisord.log +pidfile=/var/run/supervisord.pid + +[program:nginx] +command=nginx -g "daemon off;" +autostart=true +autorestart=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +[program:app] +command=pm2-runtime start build/app.js --name app +directory=/app +autostart=true +autorestart=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +environment=NODE_ENV=prod +EOF + +ENV NODE_ENV=prod + +EXPOSE 80 +EXPOSE 60000 + +# 启动时创建必要目录(防止 volume 挂载覆盖) +CMD sh -c "mkdir -p /var/log/nginx /var/lib/nginx/logs && exec supervisord -c /etc/supervisord.conf" diff --git a/docker/docker-compose.local.yml b/docker/docker-compose.local.yml new file mode 100644 index 0000000..cf54b76 --- /dev/null +++ b/docker/docker-compose.local.yml @@ -0,0 +1,24 @@ +# 本地打包测试用,使用本地源码构建 +# 用法: docker-compose -f docker/docker-compose.local.yml up -d --build + +services: + toonflow: + build: + context: .. + dockerfile: docker/Dockerfile.local + image: toonflow:local + container_name: toonflow-local + restart: unless-stopped + ports: + - "8080:80" + - "60000:60000" + environment: + - NODE_ENV=prod + volumes: + - ../logs:/var/log + healthcheck: + test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:80/"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..d70050c --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,26 @@ +services: + toonflow: + build: + context: .. + dockerfile: docker/Dockerfile + args: + GIT: ${GIT:-github} + TAG: ${TAG:-} + BRANCH: ${BRANCH:-} + image: toonflow:${TAG:-latest} + container_name: toonflow + restart: unless-stopped + ports: + - "80" + - "60000:60000" + environment: + - NODE_ENV=prod + volumes: + # 可选: 持久化日志 + - ../logs:/var/log + healthcheck: + test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:80/"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s diff --git a/docs/chat6QR.jpg b/docs/chat6QR.jpg deleted file mode 100644 index be531c3..0000000 Binary files a/docs/chat6QR.jpg and /dev/null differ diff --git a/docs/chat8QR.jpg b/docs/chat8QR.jpg new file mode 100644 index 0000000..12cf4f3 Binary files /dev/null and b/docs/chat8QR.jpg differ diff --git a/electron-builder.yml b/electron-builder.yml index 20b083a..40b752b 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -9,6 +9,7 @@ directories: files: - build/**/* - scripts/web/**/* + - env/**/* - package.json - node_modules/**/* - "!node_modules/**/*.{md,ts,map}" diff --git a/env/.env.dev b/env/.env.dev new file mode 100644 index 0000000..b8dd44a --- /dev/null +++ b/env/.env.dev @@ -0,0 +1,4 @@ +NODE_ENV=dev +PORT=60000 +OSSURL=http://127.0.0.1:60000/ + diff --git a/env/.env.prod b/env/.env.prod new file mode 100644 index 0000000..bbf0003 --- /dev/null +++ b/env/.env.prod @@ -0,0 +1,4 @@ +NODE_ENV=prod +PORT=60000 +OSSURL=http://127.0.0.1:60000/ + diff --git a/package.json b/package.json index bd626dc..b9f9a9f 100644 --- a/package.json +++ b/package.json @@ -21,13 +21,15 @@ "dev": "nodemon --inspect --exec tsx src/app.ts", "dev:gui": "chcp 65001 && electronmon -r tsx scripts/main.ts", "lint": "tsc --noEmit", - "build": "tsx scripts/build.ts", + "build": "cross-env NODE_ENV=prod tsx scripts/build.ts", "pack": "electron-builder --dir", "dist": "yarn build && electron-builder", "dist:win": "yarn build && electron-builder --win", "dist:mac": "yarn build && electron-builder --mac", "dist:linux": "yarn build && electron-builder --linux", - "test": "node build/app.js", + "test": "cross-env NODE_ENV=prod node build/app.js", + "docker:build": "docker-compose -f docker/docker-compose.yml up -d --build", + "docker:local": "docker-compose -f docker/docker-compose.local.yml up -d --build", "debug:ai": "npx @ai-sdk/devtools", "license": "bun run scripts/license.ts" }, @@ -71,6 +73,7 @@ "@types/jsonwebtoken": "^9.0.10", "@types/license-checker": "^25.0.6", "@types/morgan": "^1.9.10", + "cross-env": "^10.1.0", "electron": "^40.0.0", "electron-builder": "^26.4.0", "electronmon": "^2.0.4", diff --git a/scripts/build.ts b/scripts/build.ts index 5f57955..11638e8 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -1,4 +1,23 @@ import esbuild from "esbuild"; +import fs from "fs"; +import path from "path"; + +// 打包默认使用 prod 环境变量 +if (!process.env.NODE_ENV) { + process.env.NODE_ENV = "prod"; +} + +// 自动创建 env 目录和环境变量文件(.gitignore 可能忽略了这些文件) +const envDir = path.resolve("env"); +const envFile = path.join(envDir, `.env.${process.env.NODE_ENV}`); +if (!fs.existsSync(envDir)) { + fs.mkdirSync(envDir, { recursive: true }); +} +if (!fs.existsSync(envFile)) { + const defaultEnv = `NODE_ENV=${process.env.NODE_ENV}\nPORT=60000\nOSSURL=http://127.0.0.1:60000/\n`; + fs.writeFileSync(envFile, defaultEnv, "utf8"); + console.log(`📄 已自动创建环境变量文件: ${envFile}`); +} const external = ["electron", "sqlite3", "better-sqlite3", "mysql", "mysql2", "pg", "pg-query-stream", "oracledb", "tedious", "mssql"]; diff --git a/src/env.ts b/src/env.ts index 3cfbfad..069a4ee 100644 --- a/src/env.ts +++ b/src/env.ts @@ -1,37 +1,35 @@ -import { readFileSync, existsSync, writeFileSync } from "fs"; +import { readFileSync, existsSync, writeFileSync, mkdirSync } from "fs"; +import path from "path"; -function createDefaultEnvFile(path: string) { - const defaultContent = ["# 环境变量配置", "NODE_ENV=dev"].join("\n"); - writeFileSync(path, defaultContent, { encoding: "utf8" }); - console.log(`[环境变量]: 已创建默认的 ${path}`); -} +// 默认环境变量(当 env 文件不存在时自动创建) +const defaultEnvValues: Record