diff --git a/sync-server/docker-compose.caddy.yml b/sync-server/docker-compose.caddy.yml index 647ed4b..657d26e 100644 --- a/sync-server/docker-compose.caddy.yml +++ b/sync-server/docker-compose.caddy.yml @@ -13,7 +13,10 @@ services: # 无需 ports 映射:Caddy 通过容器网络按服务名直连(footprints-sync:8788) volumes: - ./data:/data - - ./www:/app/www:ro # 前端直读仓库目录:git pull 即生效,无需重建镜像 + - ./www:/app/www:ro # 前端直读仓库目录:git pull 即生效,无需重建镜像 + - ./src:/app/src:ro # 后端源码直读(零依赖纯 .mjs) + - ./schema.sql:/app/schema.sql:ro + - ../app/server/db.mjs:/app/src/app-commands.mjs:ro # 命令层以仓库数据层为准 environment: - REGISTER_ENABLED=true - INVITE_CODE=please-change-me # 多用户:注册须携带此邀请码(留空则不校验) diff --git a/sync-server/src/auth.mjs b/sync-server/src/auth.mjs index 2886bd5..0870e93 100644 --- a/sync-server/src/auth.mjs +++ b/sync-server/src/auth.mjs @@ -2,10 +2,10 @@ import crypto from 'node:crypto'; import { getDb } from './db.mjs'; -export function register({ username, password, device_name, invite_code }) { +export function register({ username, password, device_name, invite_code, seed }) { const d = getDb(); // 多用户管控:管理员颁发邀请码;env INVITE_CODE 作为静态兜底 - const maxUsers = Number(process.env.MAX_USERS || 0); + const maxUsers = seed ? 0 : Number(process.env.MAX_USERS || 0); // seed 播种不受名额限制 if (maxUsers > 0) { const cnt = d.prepare('SELECT COUNT(*) AS c FROM users').get().c; if (cnt >= maxUsers) throw new Error('注册名额已满,请联系管理员'); @@ -14,16 +14,18 @@ export function register({ username, password, device_name, invite_code }) { let isAdmin = 0; if (userCount === 0) { isAdmin = 1; // 首位注册者自动成为管理员 + } else if (seed) { + // 内置演示账号播种:不要求邀请码 } else { const code = String(invite_code || '').trim().toUpperCase(); - if (code) { - const row = d.prepare('SELECT id, valid FROM invite_codes WHERE code=?').get(code); - if (!row || !row.valid) throw new Error('邀请码无效或已使用'); + const envCode = String(process.env.INVITE_CODE || '').trim().toUpperCase(); + const row = code ? d.prepare('SELECT id, valid FROM invite_codes WHERE code=?').get(code) : null; + if (row && row.valid) { var __usedInviteId = row.id; // eslint-disable-line no-var - } else if (process.env.INVITE_CODE && invite_code === process.env.INVITE_CODE) { - // 静态兜底码 + } else if (code && envCode && code === envCode) { + // 静态兜底码(env INVITE_CODE),与动态邀请码等价 } else { - throw new Error('需要邀请码,请联系管理员'); + throw new Error(code ? '邀请码无效或已使用' : '需要邀请码,请联系管理员'); } } if (!/^[a-zA-Z0-9_-]{3,32}$/.test(username || '')) throw new Error('用户名需为 3-32 位字母数字-_'); diff --git a/sync-server/src/index.mjs b/sync-server/src/index.mjs index 17a3d5c..f597cfa 100644 --- a/sync-server/src/index.mjs +++ b/sync-server/src/index.mjs @@ -209,12 +209,17 @@ function readJson(req) { getDb(); -// 种子演示账号(demo / demo1234,管理员权限),便于部署后立即可用 +// 种子演示账号(demo / demo1234);seed 标记绕过邀请码/名额校验,失败原因打出日志 try { - const { register } = await import('./auth.mjs'); - register({ username: 'demo', password: 'demo1234', device_name: 'seed' }); - console.log('[footprints-sync] demo 账号已就绪 (demo / demo1234)'); -} catch { /* 已存在则跳过 */ } + const d = getDb(); + if (!d.prepare('SELECT 1 FROM users WHERE username=?').get('demo')) { + const { register } = await import('./auth.mjs'); + register({ username: 'demo', password: 'demo1234', device_name: 'seed-demo', seed: true }); + console.log('[footprints-sync] demo 账号已创建 (demo / demo1234)'); + } else { + console.log('[footprints-sync] demo 账号已存在 (demo / demo1234)'); + } +} catch (e) { console.log('[footprints-sync] demo 种子跳过:', e.message); } server.listen(PORT, '0.0.0.0', () => { console.log('[footprints-sync] listening on 0.0.0.0:' + PORT);