Spaces:
Paused
Paused
| 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. 【原生绝杀】:在容器启动前,动态往核心代码里塞入一段 Basic Auth 拦截逻辑 | |
| CMD ["sh", "-c", "rm -rf /app/server/data && ln -s /data/freellm /app/server/data && \ | |
| # 动态把密码拦截代码注入到后端的入口文件中 \ | |
| node -e \" \ | |
| const fs = require('fs'); \ | |
| const file = 'server/dist/index.js'; \ | |
| if (fs.existsSync(file)) { \ | |
| let content = fs.readFileSync(file, 'utf8'); \ | |
| const injectCode = ` \ | |
| // 密码锁中间件 \ | |
| global.authMiddleware = (req, res, next) => { \ | |
| const user = process.env.SPACE_BASIC_AUTH_USERNAME || 'admin'; \ | |
| const pass = process.env.SPACE_BASIC_AUTH_PASSWORD || 'admin123'; \ | |
| const auth = { login: user, password: pass }; \ | |
| const b64auth = (req.headers.authorization || '').split(' ')[1] || ''; \ | |
| const [login, password] = Buffer.from(b64auth, 'base64').toString().split(':'); \ | |
| if (req.url.startsWith('/v1')) return next(); \ | |
| if (login && password && login === auth.login && password === auth.password) return next(); \ | |
| res.statusCode = 401; \ | |
| res.setHeader('WWW-Authenticate', 'Basic realm=\\\"Secure Area\\\"'); \ | |
| res.end('Unauthorized'); \ | |
| }; \ | |
| `; \ | |
| content = injectCode + content.replace('const app =', 'const app = ; app |