根据官方Dockerfile修改而来 拉取项目后在根目录新建并执行下面的shell脚本 执行完毕后拷贝生成的app目录到任意有node的服务器即可运行 具体使用方法见脚本执行完毕后的输出内容 ``` #!/bin/bash # ============================================================================= # build.sh — 无需 Docker,将项目构建为可移植的 app/ 目录 # 用法: bash build.sh [选项] # # 选项: # --use-cn-mirror 使用国内镜像(npmmirror) # --base-path NEXT_PUBLIC_BASE_PATH # --sentry-dsn NEXT_PUBLIC_SENTRY_DSN # --feature-flags FEATURE_FLAGS # --output-dir 输出目录(默认: ./app) # -h, --help 显示帮助 # ============================================================================= USE_CN_MIRROR=false NEXT_PUBLIC_BASE_PATH="" NEXT_PUBLIC_SENTRY_DSN="" NEXT_PUBLIC_ANALYTICS_POSTHOG="" NEXT_PUBLIC_POSTHOG_HOST="" NEXT_PUBLIC_POSTHOG_KEY="" NEXT_PUBLIC_ANALYTICS_UMAMI="" NEXT_PUBLIC_UMAMI_SCRIPT_URL="" NEXT_PUBLIC_UMAMI_WEBSITE_ID="" FEATURE_FLAGS="" OUTPUT_DIR="$(pwd)/app" while [[ $# -gt 0 ]]; do case $1 in --use-cn-mirror) USE_CN_MIRROR=true; shift ;; --base-path) NEXT_PUBLIC_BASE_PATH="$2"; shift 2 ;; --sentry-dsn) NEXT_PUBLIC_SENTRY_DSN="$2"; shift 2 ;; --feature-flags) FEATURE_FLAGS="$2"; shift 2 ;; --output-dir) OUTPUT_DIR="$2"; shift 2 ;; -h|--help) sed -n '2,12p' "$0" | sed 's/^# \?//'; exit 0 ;; *) echo "未知参数: $1"; exit 1 ;; esac done REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" DEPS_DIR="$(mktemp -d)" trap 'rm -rf "$DEPS_DIR"' EXIT log() { echo "[$(date '+%H:%M:%S')] $*"; } die() { echo "❌ $*" >&2; exit 1; } ok() { echo "✅ $*"; } # --------------------------------------------------------------------------- # # 1. 前置检查 # --------------------------------------------------------------------------- # log "检查依赖工具..." command -v node >/dev/null || die "未找到 node,请先安装 Node.js 18+" command -v npm >/dev/null || die "未找到 npm" NODE_MAJOR=$(node -e 'process.stdout.write(process.versions.node.split(".")[0])') [[ "$NODE_MAJOR" -ge 18 ]] || die "需要 Node.js >= 18,当前版本: $(node -v)" ok "Node $(node -v) / npm $(npm -v)" # --------------------------------------------------------------------------- # # 2. 配置镜像(可选) # --------------------------------------------------------------------------- # if [[ "$USE_CN_MIRROR" == "true" ]]; then log "启用国内镜像 (npmmirror)..." export SENTRYCLI_CDNURL="https://npmmirror.com/mirrors/sentry-cli" npm config set registry "https://registry.npmmirror.com/" grep -q "canvas_binary_host_mirror" "$REPO_ROOT/.npmrc" 2>/dev/null \ || echo "canvas_binary_host_mirror=https://npmmirror.com/mirrors/canvas" >> "$REPO_ROOT/.npmrc" fi # --------------------------------------------------------------------------- # # 3. 安装/启用 pnpm(非交互模式) # --------------------------------------------------------------------------- # log "启用 corepack / pnpm..." COREPACK_NPM_REGISTRY=$(npm config get registry | sed 's/\/$//') export COREPACK_NPM_REGISTRY export COREPACK_ENABLE_STRICT=0 export CI=1 command -v corepack >/dev/null 2>&1 || npm i -g corepack@latest --silent corepack enable PKG_MANAGER=$(node -e "const p=require('$REPO_ROOT/package.json');process.stdout.write(p.packageManager||'')") [[ -n "$PKG_MANAGER" ]] || die "package.json 中未找到 packageManager 字段" PM_NAME=$(echo "$PKG_MANAGER" | cut -d'@' -f1) PM_VERSION=$(echo "$PKG_MANAGER" | cut -d'@' -f2 | cut -d'+' -f1) log "激活 $PM_NAME@$PM_VERSION(非交互模式)..." corepack prepare "${PM_NAME}@${PM_VERSION}" --activate ok "包管理器: $PKG_MANAGER" # --------------------------------------------------------------------------- # # 4. 安装项目依赖 # --------------------------------------------------------------------------- # log "安装项目依赖 (pnpm i)..." (cd "$REPO_ROOT" && pnpm i) # --------------------------------------------------------------------------- # # 5. 安装运行时依赖(pg + drizzle-orm) # --------------------------------------------------------------------------- # log "安装运行时依赖 (pg, drizzle-orm)..." echo '{"name":"deps","private":true}' > "$DEPS_DIR/package.json" (cd "$DEPS_DIR" && pnpm add pg drizzle-orm) ok "运行时依赖已安装" # --------------------------------------------------------------------------- # # 6. 设置构建环境变量 # --------------------------------------------------------------------------- # export NEXT_PUBLIC_BASE_PATH FEATURE_FLAGS NEXT_PUBLIC_SENTRY_DSN export SENTRY_ORG="" SENTRY_PROJECT="" export NEXT_PUBLIC_ANALYTICS_POSTHOG NEXT_PUBLIC_POSTHOG_HOST NEXT_PUBLIC_POSTHOG_KEY export NEXT_PUBLIC_ANALYTICS_UMAMI NEXT_PUBLIC_UMAMI_SCRIPT_URL NEXT_PUBLIC_UMAMI_WEBSITE_ID export APP_URL="http://app.com" export DATABASE_DRIVER="node" export DATABASE_URL="postgres://postgres:password@localhost:5432/postgres" export KEY_VAULTS_SECRET="use-for-build" export AUTH_SECRET="use-for-build" export NODE_OPTIONS="--max-old-space-size=8192" # --------------------------------------------------------------------------- # # 7. Prebuild # --------------------------------------------------------------------------- # log "运行 prebuild 脚本..." (cd "$REPO_ROOT" && pnpm exec tsx scripts/dockerPrebuild.mts) log "移除 desktop-only 路由..." rm -rf "$REPO_ROOT/src/app/desktop" "$REPO_ROOT/src/app/(backend)/trpc/desktop" # --------------------------------------------------------------------------- # # 8. 构建 # --------------------------------------------------------------------------- # log "开始构建 (npm run build:docker)..." (cd "$REPO_ROOT" && npm run build:docker) ok "构建完成" # --------------------------------------------------------------------------- # # 9. 组装 app/ 目录 # --------------------------------------------------------------------------- # log "组装输出目录: $OUTPUT_DIR" rm -rf "$OUTPUT_DIR" mkdir -p "$OUTPUT_DIR" copy_into() { local src="$1" dst="$2" mkdir -p "$dst" (cd "$src" && tar -cf - .) | (cd "$dst" && tar -xf -) } log " → standalone 输出..." copy_into "$REPO_ROOT/.next/standalone" "$OUTPUT_DIR" log " → .next/static..." copy_into "$REPO_ROOT/.next/static" "$OUTPUT_DIR/.next/static" [[ -d "$REPO_ROOT/public/_spa" ]] && { log " → public/_spa..."; copy_into "$REPO_ROOT/public/_spa" "$OUTPUT_DIR/public/_spa"; } log " → migrations..." copy_into "$REPO_ROOT/packages/database/migrations" "$OUTPUT_DIR/migrations" cp "$REPO_ROOT/scripts/migrateServerDB/docker.cjs" "$OUTPUT_DIR/docker.cjs" cp "$REPO_ROOT/scripts/migrateServerDB/errorHint.js" "$OUTPUT_DIR/errorHint.js" log " → 运行时依赖 (pg, drizzle-orm)..." copy_into "$DEPS_DIR/node_modules/.pnpm" "$OUTPUT_DIR/node_modules/.pnpm" copy_into "$DEPS_DIR/node_modules/pg" "$OUTPUT_DIR/node_modules/pg" copy_into "$DEPS_DIR/node_modules/drizzle-orm" "$OUTPUT_DIR/node_modules/drizzle-orm" cp "$REPO_ROOT/scripts/serverLauncher/startServer.js" "$OUTPUT_DIR/startServer.js" log " → scripts/_shared..." copy_into "$REPO_ROOT/scripts/_shared" "$OUTPUT_DIR/scripts/_shared" ok "文件组装完成" # --------------------------------------------------------------------------- # # 10. 修复 startServer.js / docker.cjs 中硬编码的 Docker 路径 /app/ # # Docker 容器里应用固定运行在 /app/,源码直接写死了绝对路径,例如: # require('/app/scripts/_shared/checkDeprecatedAuth.js') # 宿主机部署路径不同,改为基于 __dirname 的动态路径。 # --------------------------------------------------------------------------- # log "修补硬编码路径 /app/ → 动态 __dirname..." patch_js_file() { local f="$1" [[ -f "$f" ]] || return 0 node - "$f" << 'NODEEOF' const fs = require('fs'); const path = require('path'); const file = process.argv[2]; let src = fs.readFileSync(file, 'utf8'); if (!src.includes('/app/') && !src.includes("'/app'") && !src.includes('"/app"')) { console.log(' skip:', path.basename(file)); process.exit(0); } // 在顶部注入 __appDir(只注一次) if (!src.includes('__appDir')) { const DECL = "const __appDir = require('path').resolve(__dirname);\n"; // 插在 'use strict' / shebang / 注释块之后,第一行实际代码之前 src = src.replace(/^((?:\s*\/\/[^\n]*\n|'use strict';\n|"use strict";\n)*)/, (m) => m + DECL); } // require('/app/foo') → require(__appDir + '/foo') src = src.replace(/require\(['"]\/app\/([^'"]*)['"]\)/g, (_, rest) => `require(__appDir + '/${rest}')`); // require('/app') → require(__appDir) src = src.replace(/require\(['"]\/app['"]\)/g, 'require(__appDir)'); // 路径字符串 '/app/foo' → (__appDir + '/foo') // 仅替换出现在 赋值/参数/运算 上下文中的 src = src.replace(/([\s=,(+\[!])['"]\/app\/([^'"]*)['"]/g, (_, pre, rest) => `${pre}(__appDir + '/${rest}')`); // 路径字符串 '/app' → __appDir src = src.replace(/([\s=,(+\[!])['"]\/app['"]/g, (_, pre) => `${pre}__appDir`); // '/bin/node' → process.execPath // Docker 容器里 node 在 /bin/node,宿主机路径不同,改为动态取当前 node 路径 src = src.replace("['/bin/proxychains', '-q', '/bin/node', scriptPath]", "['/bin/proxychains', '-q', process.execPath, scriptPath]"); src = src.replace("['/bin/node', scriptPath]", "[process.execPath, scriptPath]"); fs.writeFileSync(file, src, 'utf8'); console.log(' patched:', path.basename(file)); NODEEOF } patch_js_file "$OUTPUT_DIR/startServer.js" patch_js_file "$OUTPUT_DIR/docker.cjs" ok "路径修补完成" # --------------------------------------------------------------------------- # # 11. 生成 start.sh 启动脚本 # --------------------------------------------------------------------------- # cat > "$OUTPUT_DIR/start.sh" << 'STARTSCRIPT' #!/usr/bin/env bash # ============================================================================= # start.sh — 启动服务(将 app/ 目录复制到任意服务器后执行此脚本) # # 必填环境变量(可在下方直接填写,或由外部 export 注入): # APP_URL 对外访问的完整 URL,例如 https://chat.example.com # DATABASE_URL PostgreSQL 连接串,例如 postgres://user:pass@host/db # KEY_VAULTS_SECRET 密钥加密密钥(建议 32 位随机字符串) # AUTH_SECRET Better-Auth 签名密钥(建议 32 位随机字符串) # ============================================================================= set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # ── 在此处填写生产环境变量 ───────────────────────────────────────────────── # : "${APP_URL:=}" : "${DATABASE_URL:=}" : "${KEY_VAULTS_SECRET:=}" : "${AUTH_SECRET:=}" # ─────────────────────────────────────────────────────────────────────────── # export NODE_ENV="production" export NODE_OPTIONS="${NODE_OPTIONS:---dns-result-order=ipv4first --use-openssl-ca}" export DATABASE_DRIVER="${DATABASE_DRIVER:-node}" export HOSTNAME="${HOSTNAME:-0.0.0.0}" export PORT="${PORT:-3210}" export MIDDLEWARE_REWRITE_THROUGH_LOCAL="${MIDDLEWARE_REWRITE_THROUGH_LOCAL:-1}" export APP_URL DATABASE_URL KEY_VAULTS_SECRET AUTH_SECRET MISSING=() [[ -z "$APP_URL" ]] && MISSING+=("APP_URL") [[ -z "$DATABASE_URL" ]] && MISSING+=("DATABASE_URL") [[ -z "$KEY_VAULTS_SECRET" ]] && MISSING+=("KEY_VAULTS_SECRET") [[ -z "$AUTH_SECRET" ]] && MISSING+=("AUTH_SECRET") if [[ ${#MISSING[@]} -gt 0 ]]; then echo "❌ 缺少必填环境变量: ${MISSING[*]}" echo " 请在 start.sh 顶部设置,或通过 export 注入后执行" exit 1 fi echo "🚀 启动服务 — 监听 ${HOSTNAME}:${PORT}" exec node "$SCRIPT_DIR/startServer.js" STARTSCRIPT chmod +x "$OUTPUT_DIR/start.sh" cat <= 18 3. 编辑 app/start.sh 填入生产环境变量,或 export 后执行: APP_URL=https://chat.example.com \\ DATABASE_URL=postgres://user:pass@host/db \\ KEY_VAULTS_SECRET=<随机字符串> \\ AUTH_SECRET=<随机字符串> \\ bash app/start.sh 4. 或者直接执行node startServer.js 默认端口: 3210(通过 PORT 环境变量修改) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ EOF ``` Loading... 根据官方Dockerfile修改而来 拉取项目后在根目录新建并执行下面的shell脚本 执行完毕后拷贝生成的app目录到任意有node的服务器即可运行 具体使用方法见脚本执行完毕后的输出内容 ``` #!/bin/bash # ============================================================================= # build.sh — 无需 Docker,将项目构建为可移植的 app/ 目录 # 用法: bash build.sh [选项] # # 选项: # --use-cn-mirror 使用国内镜像(npmmirror) # --base-path <path> NEXT_PUBLIC_BASE_PATH # --sentry-dsn <dsn> NEXT_PUBLIC_SENTRY_DSN # --feature-flags <flags> FEATURE_FLAGS # --output-dir <dir> 输出目录(默认: ./app) # -h, --help 显示帮助 # ============================================================================= USE_CN_MIRROR=false NEXT_PUBLIC_BASE_PATH="" NEXT_PUBLIC_SENTRY_DSN="" NEXT_PUBLIC_ANALYTICS_POSTHOG="" NEXT_PUBLIC_POSTHOG_HOST="" NEXT_PUBLIC_POSTHOG_KEY="" NEXT_PUBLIC_ANALYTICS_UMAMI="" NEXT_PUBLIC_UMAMI_SCRIPT_URL="" NEXT_PUBLIC_UMAMI_WEBSITE_ID="" FEATURE_FLAGS="" OUTPUT_DIR="$(pwd)/app" while [[ $# -gt 0 ]]; do case $1 in --use-cn-mirror) USE_CN_MIRROR=true; shift ;; --base-path) NEXT_PUBLIC_BASE_PATH="$2"; shift 2 ;; --sentry-dsn) NEXT_PUBLIC_SENTRY_DSN="$2"; shift 2 ;; --feature-flags) FEATURE_FLAGS="$2"; shift 2 ;; --output-dir) OUTPUT_DIR="$2"; shift 2 ;; -h|--help) sed -n '2,12p' "$0" | sed 's/^# \?//'; exit 0 ;; *) echo "未知参数: $1"; exit 1 ;; esac done REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" DEPS_DIR="$(mktemp -d)" trap 'rm -rf "$DEPS_DIR"' EXIT log() { echo "[$(date '+%H:%M:%S')] $*"; } die() { echo "❌ $*" >&2; exit 1; } ok() { echo "✅ $*"; } # --------------------------------------------------------------------------- # # 1. 前置检查 # --------------------------------------------------------------------------- # log "检查依赖工具..." command -v node >/dev/null || die "未找到 node,请先安装 Node.js 18+" command -v npm >/dev/null || die "未找到 npm" NODE_MAJOR=$(node -e 'process.stdout.write(process.versions.node.split(".")[0])') [[ "$NODE_MAJOR" -ge 18 ]] || die "需要 Node.js >= 18,当前版本: $(node -v)" ok "Node $(node -v) / npm $(npm -v)" # --------------------------------------------------------------------------- # # 2. 配置镜像(可选) # --------------------------------------------------------------------------- # if [[ "$USE_CN_MIRROR" == "true" ]]; then log "启用国内镜像 (npmmirror)..." export SENTRYCLI_CDNURL="https://npmmirror.com/mirrors/sentry-cli" npm config set registry "https://registry.npmmirror.com/" grep -q "canvas_binary_host_mirror" "$REPO_ROOT/.npmrc" 2>/dev/null \ || echo "canvas_binary_host_mirror=https://npmmirror.com/mirrors/canvas" >> "$REPO_ROOT/.npmrc" fi # --------------------------------------------------------------------------- # # 3. 安装/启用 pnpm(非交互模式) # --------------------------------------------------------------------------- # log "启用 corepack / pnpm..." COREPACK_NPM_REGISTRY=$(npm config get registry | sed 's/\/$//') export COREPACK_NPM_REGISTRY export COREPACK_ENABLE_STRICT=0 export CI=1 command -v corepack >/dev/null 2>&1 || npm i -g corepack@latest --silent corepack enable PKG_MANAGER=$(node -e "const p=require('$REPO_ROOT/package.json');process.stdout.write(p.packageManager||'')") [[ -n "$PKG_MANAGER" ]] || die "package.json 中未找到 packageManager 字段" PM_NAME=$(echo "$PKG_MANAGER" | cut -d'@' -f1) PM_VERSION=$(echo "$PKG_MANAGER" | cut -d'@' -f2 | cut -d'+' -f1) log "激活 $PM_NAME@$PM_VERSION(非交互模式)..." corepack prepare "${PM_NAME}@${PM_VERSION}" --activate ok "包管理器: $PKG_MANAGER" # --------------------------------------------------------------------------- # # 4. 安装项目依赖 # --------------------------------------------------------------------------- # log "安装项目依赖 (pnpm i)..." (cd "$REPO_ROOT" && pnpm i) # --------------------------------------------------------------------------- # # 5. 安装运行时依赖(pg + drizzle-orm) # --------------------------------------------------------------------------- # log "安装运行时依赖 (pg, drizzle-orm)..." echo '{"name":"deps","private":true}' > "$DEPS_DIR/package.json" (cd "$DEPS_DIR" && pnpm add pg drizzle-orm) ok "运行时依赖已安装" # --------------------------------------------------------------------------- # # 6. 设置构建环境变量 # --------------------------------------------------------------------------- # export NEXT_PUBLIC_BASE_PATH FEATURE_FLAGS NEXT_PUBLIC_SENTRY_DSN export SENTRY_ORG="" SENTRY_PROJECT="" export NEXT_PUBLIC_ANALYTICS_POSTHOG NEXT_PUBLIC_POSTHOG_HOST NEXT_PUBLIC_POSTHOG_KEY export NEXT_PUBLIC_ANALYTICS_UMAMI NEXT_PUBLIC_UMAMI_SCRIPT_URL NEXT_PUBLIC_UMAMI_WEBSITE_ID export APP_URL="http://app.com" export DATABASE_DRIVER="node" export DATABASE_URL="postgres://postgres:password@localhost:5432/postgres" export KEY_VAULTS_SECRET="use-for-build" export AUTH_SECRET="use-for-build" export NODE_OPTIONS="--max-old-space-size=8192" # --------------------------------------------------------------------------- # # 7. Prebuild # --------------------------------------------------------------------------- # log "运行 prebuild 脚本..." (cd "$REPO_ROOT" && pnpm exec tsx scripts/dockerPrebuild.mts) log "移除 desktop-only 路由..." rm -rf "$REPO_ROOT/src/app/desktop" "$REPO_ROOT/src/app/(backend)/trpc/desktop" # --------------------------------------------------------------------------- # # 8. 构建 # --------------------------------------------------------------------------- # log "开始构建 (npm run build:docker)..." (cd "$REPO_ROOT" && npm run build:docker) ok "构建完成" # --------------------------------------------------------------------------- # # 9. 组装 app/ 目录 # --------------------------------------------------------------------------- # log "组装输出目录: $OUTPUT_DIR" rm -rf "$OUTPUT_DIR" mkdir -p "$OUTPUT_DIR" copy_into() { local src="$1" dst="$2" mkdir -p "$dst" (cd "$src" && tar -cf - .) | (cd "$dst" && tar -xf -) } log " → standalone 输出..." copy_into "$REPO_ROOT/.next/standalone" "$OUTPUT_DIR" log " → .next/static..." copy_into "$REPO_ROOT/.next/static" "$OUTPUT_DIR/.next/static" [[ -d "$REPO_ROOT/public/_spa" ]] && { log " → public/_spa..."; copy_into "$REPO_ROOT/public/_spa" "$OUTPUT_DIR/public/_spa"; } log " → migrations..." copy_into "$REPO_ROOT/packages/database/migrations" "$OUTPUT_DIR/migrations" cp "$REPO_ROOT/scripts/migrateServerDB/docker.cjs" "$OUTPUT_DIR/docker.cjs" cp "$REPO_ROOT/scripts/migrateServerDB/errorHint.js" "$OUTPUT_DIR/errorHint.js" log " → 运行时依赖 (pg, drizzle-orm)..." copy_into "$DEPS_DIR/node_modules/.pnpm" "$OUTPUT_DIR/node_modules/.pnpm" copy_into "$DEPS_DIR/node_modules/pg" "$OUTPUT_DIR/node_modules/pg" copy_into "$DEPS_DIR/node_modules/drizzle-orm" "$OUTPUT_DIR/node_modules/drizzle-orm" cp "$REPO_ROOT/scripts/serverLauncher/startServer.js" "$OUTPUT_DIR/startServer.js" log " → scripts/_shared..." copy_into "$REPO_ROOT/scripts/_shared" "$OUTPUT_DIR/scripts/_shared" ok "文件组装完成" # --------------------------------------------------------------------------- # # 10. 修复 startServer.js / docker.cjs 中硬编码的 Docker 路径 /app/ # # Docker 容器里应用固定运行在 /app/,源码直接写死了绝对路径,例如: # require('/app/scripts/_shared/checkDeprecatedAuth.js') # 宿主机部署路径不同,改为基于 __dirname 的动态路径。 # --------------------------------------------------------------------------- # log "修补硬编码路径 /app/ → 动态 __dirname..." patch_js_file() { local f="$1" [[ -f "$f" ]] || return 0 node - "$f" << 'NODEEOF' const fs = require('fs'); const path = require('path'); const file = process.argv[2]; let src = fs.readFileSync(file, 'utf8'); if (!src.includes('/app/') && !src.includes("'/app'") && !src.includes('"/app"')) { console.log(' skip:', path.basename(file)); process.exit(0); } // 在顶部注入 __appDir(只注一次) if (!src.includes('__appDir')) { const DECL = "const __appDir = require('path').resolve(__dirname);\n"; // 插在 'use strict' / shebang / 注释块之后,第一行实际代码之前 src = src.replace(/^((?:\s*\/\/[^\n]*\n|'use strict';\n|"use strict";\n)*)/, (m) => m + DECL); } // require('/app/foo') → require(__appDir + '/foo') src = src.replace(/require\(['"]\/app\/([^'"]*)['"]\)/g, (_, rest) => `require(__appDir + '/${rest}')`); // require('/app') → require(__appDir) src = src.replace(/require\(['"]\/app['"]\)/g, 'require(__appDir)'); // 路径字符串 '/app/foo' → (__appDir + '/foo') // 仅替换出现在 赋值/参数/运算 上下文中的 src = src.replace(/([\s=,(+\[!])['"]\/app\/([^'"]*)['"]/g, (_, pre, rest) => `${pre}(__appDir + '/${rest}')`); // 路径字符串 '/app' → __appDir src = src.replace(/([\s=,(+\[!])['"]\/app['"]/g, (_, pre) => `${pre}__appDir`); // '/bin/node' → process.execPath // Docker 容器里 node 在 /bin/node,宿主机路径不同,改为动态取当前 node 路径 src = src.replace("['/bin/proxychains', '-q', '/bin/node', scriptPath]", "['/bin/proxychains', '-q', process.execPath, scriptPath]"); src = src.replace("['/bin/node', scriptPath]", "[process.execPath, scriptPath]"); fs.writeFileSync(file, src, 'utf8'); console.log(' patched:', path.basename(file)); NODEEOF } patch_js_file "$OUTPUT_DIR/startServer.js" patch_js_file "$OUTPUT_DIR/docker.cjs" ok "路径修补完成" # --------------------------------------------------------------------------- # # 11. 生成 start.sh 启动脚本 # --------------------------------------------------------------------------- # cat > "$OUTPUT_DIR/start.sh" << 'STARTSCRIPT' #!/usr/bin/env bash # ============================================================================= # start.sh — 启动服务(将 app/ 目录复制到任意服务器后执行此脚本) # # 必填环境变量(可在下方直接填写,或由外部 export 注入): # APP_URL 对外访问的完整 URL,例如 https://chat.example.com # DATABASE_URL PostgreSQL 连接串,例如 postgres://user:pass@host/db # KEY_VAULTS_SECRET 密钥加密密钥(建议 32 位随机字符串) # AUTH_SECRET Better-Auth 签名密钥(建议 32 位随机字符串) # ============================================================================= set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # ── 在此处填写生产环境变量 ───────────────────────────────────────────────── # : "${APP_URL:=}" : "${DATABASE_URL:=}" : "${KEY_VAULTS_SECRET:=}" : "${AUTH_SECRET:=}" # ─────────────────────────────────────────────────────────────────────────── # export NODE_ENV="production" export NODE_OPTIONS="${NODE_OPTIONS:---dns-result-order=ipv4first --use-openssl-ca}" export DATABASE_DRIVER="${DATABASE_DRIVER:-node}" export HOSTNAME="${HOSTNAME:-0.0.0.0}" export PORT="${PORT:-3210}" export MIDDLEWARE_REWRITE_THROUGH_LOCAL="${MIDDLEWARE_REWRITE_THROUGH_LOCAL:-1}" export APP_URL DATABASE_URL KEY_VAULTS_SECRET AUTH_SECRET MISSING=() [[ -z "$APP_URL" ]] && MISSING+=("APP_URL") [[ -z "$DATABASE_URL" ]] && MISSING+=("DATABASE_URL") [[ -z "$KEY_VAULTS_SECRET" ]] && MISSING+=("KEY_VAULTS_SECRET") [[ -z "$AUTH_SECRET" ]] && MISSING+=("AUTH_SECRET") if [[ ${#MISSING[@]} -gt 0 ]]; then echo "❌ 缺少必填环境变量: ${MISSING[*]}" echo " 请在 start.sh 顶部设置,或通过 export 注入后执行" exit 1 fi echo "🚀 启动服务 — 监听 ${HOSTNAME}:${PORT}" exec node "$SCRIPT_DIR/startServer.js" STARTSCRIPT chmod +x "$OUTPUT_DIR/start.sh" cat <<EOF ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ✅ 构建成功!输出目录: $OUTPUT_DIR ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 部署步骤: 1. 将 app/ 目录整体复制到目标服务器(保持目录结构) 2. 确保目标服务器已安装 Node.js >= 18 3. 编辑 app/start.sh 填入生产环境变量,或 export 后执行: APP_URL=https://chat.example.com \\ DATABASE_URL=postgres://user:pass@host/db \\ KEY_VAULTS_SECRET=<随机字符串> \\ AUTH_SECRET=<随机字符串> \\ bash app/start.sh 4. 或者直接执行node startServer.js 默认端口: 3210(通过 PORT 环境变量修改) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ EOF ``` 最后修改:2026 年 05 月 19 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 如果觉得我的文章对你有用,请随意赞赏