FROM node:20-slim AS builder WORKDIR /app RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/* RUN git clone https://github.com/tashfeenahmed/freellmapi.git . RUN npm install RUN npm run build # --- 生产运行环境 --- FROM node:20-slim AS runner WORKDIR /app COPY --from=builder /app ./ RUN mkdir -p /data/freellm # 1. 前端静态路由修复 RUN cp -r client/dist/* server/dist/public/ 2>/dev/null || cp -r client/dist/* server/public/ 2>/dev/null || true # 2. 注入基础配置 EXPOSE 7860 ENV PORT=7860 ENV NODE_ENV=production ENV DATABASE_URL="file:/data/database.sqlite" # 3. 【无损看门狗】:直接把拦截逻辑写进一个独立的中间件,然后利用 Node.js 极低层的 http 模块拦截器 # 无论项目底层怎么写、变量叫什么名字,只要请求进来,首先必过此关,绝不引发任何语法冲突! RUN echo "import fs from 'fs';" > security.js && \ echo "const file = 'server/dist/index.js';" >> security.js && \ echo "if (fs.existsSync(file)) {" >> security.js && \ echo " let content = fs.readFileSync(file, 'utf8');" >> security.js && \ echo " const injectCode = \` \ import http from 'http'; \ const originalCreateServer = http.createServer; \ http.createServer = function(onion) { \ return originalCreateServer.call(this, (req, res) => { \ const user = process.env.SPACE_BASIC_AUTH_USERNAME || 'admin'; \ const pass = process.env.SPACE_BASIC_AUTH_PASSWORD || 'admin123'; \ if (req.url.startsWith('/v1')) return onion(req, res); \ const b64auth = (req.headers.authorization || '').split(' ')[1] || ''; \ const [login, password] = Buffer.from(b64auth, 'base64').toString().split(':'); \ if (login === user && password === pass) return onion(req, res); \ res.statusCode = 401; \ res.setHeader('WWW-Authenticate', 'Basic realm=\"Secure\"'); \ res.end('Unauthorized'); \ }); \ }; \n\`;" >> security.js && \ echo " fs.writeFileSync(file, injectCode + content, 'utf8');" >> security.js && \ echo "}" >> security.js # 4. 完美运行命令 CMD ["sh", "-c", "rm -rf /app/server/data && ln -s /data/freellm /app/server/data && node security.js && export ENCRYPTION_KEY=$(node -e \"console.log(require('crypto').randomBytes(32).toString('hex'))\") && node server/dist/index.js"] # ... 前面保持你原本的 Dockerfile 不变 ... CMD node -e " \ const fs = require('fs'); \ const path = require('path'); \ const dbPath = path.join(__dirname, 'server/prisma/dev.db'); \ if (fs.existsSync(dbPath)) { \ try { \ const sqlite3 = require('sqlite3').verbose(); \ const db = new sqlite3.Database(dbPath); \ db.serialize(() => { \ const maps = { GOOGLE_API_KEY:'google', GROQ_API_KEY:'groq', GITHUB_TOKEN:'github', OPENROUTER_API_KEY:'openrouter', MISTRAL_API_KEY:'mistral', TOGETHER_API_KEY:'together', NVIDIA_API_KEY:'nvidia', COHERE_API_KEY:'cohere', HF_TOKEN:'huggingface', CEREBRAS_API_KEY:'cerebras', SAMBANOVA_API_KEY:'sambanova', CLOUDFLARE_API_TOKEN:'cloudflare', ZHIPU_API_KEY:'zhipu' }; \ const now = new Date().toISOString(); \ Object.entries(maps).forEach(([k, p]) => { \ const val = process.env[k]; \ if (!val) return; \ db.get('SELECT id FROM Provider WHERE provider = ?', [p], (err, row) => { \ if (!row) { \ db.run('INSERT INTO Provider (id, name, provider, apiKey, status, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?, ?, ?)', [require('crypto').randomUUID(), p, p, val, 'active', now, now], (e) => { \ if(!e) console.log('✅ Auto-linked: ' + p); \ }); \ } \ }); \ }); \ }); \ } catch(e) { console.log('Prisma initializing...'); } \ }" && npm run start