D135c: 铸渊执行记录 · guada部署·Outline修复·邮箱注册系统
完成: - Nginx /api/ 路由修复(3998→3090) - guada Agent引擎部署(BS-SG-001:8787) - Outline文件写入权限修复 - Dex数据库持久化+权限修复 - 邮箱注册系统(register.html + register-api:3997) - 注册服务改用Python写Dex BLOB(修复node-sqlite3格式问题) 待修复: - 注册后退出再登录间歇性OAuth state丢失(考虑独立子域名) 记录: cc-d135c.hdlp
This commit is contained in:
parent
9a35f35ea1
commit
2bfc3c547b
@ -2,111 +2,161 @@
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>注册 · 光湖语言世界</title>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>光湖 · 注册</title>
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
body{background:#0a0a14;color:#c8d6e5;font-family:'PingFang SC',-apple-system,sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh}
|
||||
.card{background:#111928;border:1px solid #1e2d4a;border-radius:16px;padding:40px;width:100%;max-width:420px}
|
||||
.card h1{font-size:24px;color:#7fd6ff;margin-bottom:4px}
|
||||
.card .sub{color:#5a7d9a;font-size:13px;margin-bottom:30px}
|
||||
.field{margin-bottom:18px}
|
||||
.field label{display:block;font-size:13px;color:#7a9bb5;margin-bottom:6px}
|
||||
.field input{width:100%;padding:12px 14px;background:#0a0e1a;border:1px solid #1e2d4a;border-radius:8px;color:#e0e8f0;font-size:14px;outline:none}
|
||||
.field input:focus{border-color:#4a7dc4}
|
||||
.field .hint{font-size:11px;color:#4a5d6e;margin-top:4px}
|
||||
.btn{width:100%;padding:12px;background:linear-gradient(135deg,#2563eb,#0e7490);border:none;border-radius:8px;color:white;font-size:15px;font-weight:600;cursor:pointer;margin-top:8px}
|
||||
.btn:hover{opacity:.9}
|
||||
:root{
|
||||
--bg:#060a14;--card:rgba(14,20,48,0.8);
|
||||
--blue:#60a5fa;--cyan:#22d3ee;--text:#eaf0ff;--dim:#94a7d0;
|
||||
--border:rgba(96,165,250,0.15);--ok:#34d399;--err:#f87171;
|
||||
}
|
||||
body{font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',sans-serif;background:var(--bg);color:var(--text);min-height:100vh;display:flex;align-items:center;justify-content:center;overflow:hidden}
|
||||
.bg{position:fixed;inset:0;background:radial-gradient(ellipse at 30% 20%,rgba(40,80,160,0.3),transparent 60%),radial-gradient(ellipse at 70% 80%,rgba(30,60,130,0.25),transparent 60%),#060a14;z-index:0}
|
||||
.card{position:relative;z-index:1;width:100%;max-width:400px;padding:40px 32px;background:var(--card);border:1px solid var(--border);border-radius:16px;backdrop-filter:blur(20px)}
|
||||
.logo{text-align:center;font-size:32px;margin-bottom:8px}
|
||||
.title{text-align:center;font-size:22px;font-weight:500;color:var(--text);margin-bottom:4px}
|
||||
.sub{text-align:center;font-size:13px;color:var(--dim);margin-bottom:28px}
|
||||
.step{display:none}
|
||||
.step.active{display:block}
|
||||
label{display:block;font-size:13px;color:var(--dim);margin-bottom:6px}
|
||||
input{width:100%;padding:12px;border-radius:10px;border:1px solid var(--border);background:rgba(12,20,40,0.6);color:var(--text);font-size:15px;outline:none;transition:border .3s;margin-bottom:16px}
|
||||
input:focus{border-color:var(--blue)}
|
||||
.btn{width:100%;padding:12px;border-radius:10px;border:none;background:linear-gradient(135deg,var(--blue),var(--cyan));color:#fff;font-size:15px;font-weight:500;cursor:pointer;transition:opacity .3s}
|
||||
.btn:hover{opacity:.85}
|
||||
.btn:disabled{opacity:.5;cursor:not-allowed}
|
||||
.msg{padding:10px 14px;border-radius:8px;font-size:13px;margin-top:16px;display:none}
|
||||
.msg.success{background:#0d3d2a;color:#4ade80;display:block}
|
||||
.msg.error{background:#2a0d0d;color:#f87171;display:block}
|
||||
.footer{margin-top:24px;text-align:center;font-size:13px}
|
||||
.footer a{color:#4a9ec4}
|
||||
.msg{text-align:center;font-size:13px;margin-top:12px;min-height:20px}
|
||||
.msg.ok{color:var(--ok)}
|
||||
.msg.err{color:var(--err)}
|
||||
.back{display:block;text-align:center;margin-top:16px;font-size:13px;color:var(--dim);text-decoration:none}
|
||||
.back:hover{color:var(--blue)}
|
||||
.countdown{color:var(--dim)}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="bg"></div>
|
||||
<div class="card">
|
||||
<h1>🌊 注册光湖账号</h1>
|
||||
<p class="sub">注册后可进入知识库,拥有独立路径和空间</p>
|
||||
|
||||
<div class="field">
|
||||
<label>用户名</label>
|
||||
<input type="text" id="username" placeholder="用于登录和显示的昵称" autocomplete="username">
|
||||
<div class="hint">2-20个字符 · 可使用中文</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>邮箱</label>
|
||||
<div class="logo">🌊</div>
|
||||
<div class="title">注册光湖协作中心</div>
|
||||
<div class="sub">用邮箱注册,立即开始协作</div>
|
||||
|
||||
<div id="step1" class="step active">
|
||||
<label>邮箱地址</label>
|
||||
<input type="email" id="email" placeholder="your@email.com" autocomplete="email">
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>密码</label>
|
||||
<input type="password" id="password" placeholder="至少8位" autocomplete="new-password">
|
||||
<button class="btn" id="sendBtn" onclick="sendCode()">发送验证码</button>
|
||||
<div class="msg" id="msg1"></div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>确认密码</label>
|
||||
<input type="password" id="password2" placeholder="再次输入密码">
|
||||
</div>
|
||||
|
||||
<button class="btn" id="submitBtn" onclick="register()">注册</button>
|
||||
|
||||
<div class="msg" id="msg"></div>
|
||||
|
||||
<div class="footer">
|
||||
已有账号?<a href="https://guanghubingshuo.com/outline">去登录</a>
|
||||
<div id="step2" class="step">
|
||||
<label>验证码(已发送至 <span id="emailDisplay"></span>)</label>
|
||||
<input type="text" id="code" placeholder="6位验证码" maxlength="6" autocomplete="one-time-code">
|
||||
<label>设置密码</label>
|
||||
<input type="password" id="password" placeholder="至少6位">
|
||||
<button class="btn" id="verifyBtn" onclick="verifyCode()">完成注册</button>
|
||||
<div class="msg" id="msg2"></div>
|
||||
<a href="#" class="back" onclick="backToStep1()">← 换一个邮箱</a>
|
||||
</div>
|
||||
|
||||
<div class="msg" id="msg3" style="margin-top:16px"></div>
|
||||
<a href="/outline/" class="back" style="margin-top:8px">已有账号?去登录 →</a>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
async function register() {
|
||||
const btn = document.getElementById('submitBtn');
|
||||
const msg = document.getElementById('msg');
|
||||
const username = document.getElementById('username').value.trim();
|
||||
let countdownTimer = null;
|
||||
|
||||
async function sendCode() {
|
||||
const email = document.getElementById('email').value.trim();
|
||||
const password = document.getElementById('password').value;
|
||||
const password2 = document.getElementById('password2').value;
|
||||
const msg = document.getElementById('msg1');
|
||||
const btn = document.getElementById('sendBtn');
|
||||
|
||||
msg.className = 'msg';
|
||||
msg.style.display = 'none';
|
||||
if (!email || !email.includes('@')) {
|
||||
msg.className = 'msg err'; msg.textContent = '请输入有效邮箱';
|
||||
return;
|
||||
}
|
||||
|
||||
if (!username || username.length < 2) { showMsg('请输入至少2个字符的用户名', 'error'); return }
|
||||
if (!email || !email.includes('@')) { showMsg('请输入有效的邮箱地址', 'error'); return }
|
||||
if (password.length < 8) { showMsg('密码至少8位', 'error'); return }
|
||||
if (password !== password2) { showMsg('两次密码不一致', 'error'); return }
|
||||
|
||||
btn.disabled = true;
|
||||
btn.textContent = '注册中...';
|
||||
btn.disabled = true; btn.textContent = '发送中...';
|
||||
msg.className = 'msg'; msg.textContent = '';
|
||||
|
||||
try {
|
||||
const res = await fetch('https://guanghubingshuo.com/register-api/register', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({username, email, password})
|
||||
const res = await fetch('/register-api/send-code', {
|
||||
method: 'POST', headers: {'Content-Type':'application/json'},
|
||||
body: JSON.stringify({email})
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.ok) {
|
||||
showMsg('✅ 注册成功!3秒后跳转到登录页...', 'success');
|
||||
setTimeout(() => { window.location.href = 'https://guanghubingshuo.com/outline'; }, 3000);
|
||||
document.getElementById('emailDisplay').textContent = email;
|
||||
document.getElementById('step1').classList.remove('active');
|
||||
document.getElementById('step2').classList.add('active');
|
||||
startCountdown(60);
|
||||
} else {
|
||||
showMsg('❌ ' + data.error, 'error');
|
||||
btn.disabled = false;
|
||||
btn.textContent = '注册';
|
||||
msg.className = 'msg err'; msg.textContent = data.error;
|
||||
btn.disabled = false; btn.textContent = '发送验证码';
|
||||
}
|
||||
} catch(e) {
|
||||
showMsg('❌ 网络错误,请重试', 'error');
|
||||
btn.disabled = false;
|
||||
btn.textContent = '注册';
|
||||
msg.className = 'msg err'; msg.textContent = '网络错误,请重试';
|
||||
btn.disabled = false; btn.textContent = '发送验证码';
|
||||
}
|
||||
}
|
||||
|
||||
function showMsg(text, type) {
|
||||
const msg = document.getElementById('msg');
|
||||
msg.textContent = text;
|
||||
msg.className = 'msg ' + type;
|
||||
msg.style.display = 'block';
|
||||
function startCountdown(sec) {
|
||||
if (countdownTimer) clearInterval(countdownTimer);
|
||||
const btn = document.getElementById('sendBtn');
|
||||
btn.disabled = true;
|
||||
countdownTimer = setInterval(() => {
|
||||
sec--;
|
||||
if (sec <= 0) {
|
||||
clearInterval(countdownTimer);
|
||||
btn.disabled = false;
|
||||
btn.textContent = '重新发送';
|
||||
} else {
|
||||
btn.textContent = `${sec}秒后重发`;
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
async function verifyCode() {
|
||||
const email = document.getElementById('email').value.trim();
|
||||
const code = document.getElementById('code').value.trim();
|
||||
const password = document.getElementById('password').value;
|
||||
const msg = document.getElementById('msg2');
|
||||
const btn = document.getElementById('verifyBtn');
|
||||
|
||||
if (!code || code.length !== 6) {
|
||||
msg.className = 'msg err'; msg.textContent = '请输入6位验证码';
|
||||
return;
|
||||
}
|
||||
if (!password || password.length < 6) {
|
||||
msg.className = 'msg err'; msg.textContent = '密码至少6位';
|
||||
return;
|
||||
}
|
||||
|
||||
btn.disabled = true; btn.textContent = '注册中...';
|
||||
|
||||
try {
|
||||
const res = await fetch('/register-api/verify', {
|
||||
method: 'POST', headers: {'Content-Type':'application/json'},
|
||||
body: JSON.stringify({email, code, password})
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.ok) {
|
||||
msg.className = 'msg ok'; msg.textContent = '注册成功!3秒后跳转登录...';
|
||||
setTimeout(() => { window.location.href = '/outline/'; }, 3000);
|
||||
} else {
|
||||
msg.className = 'msg err'; msg.textContent = data.error;
|
||||
btn.disabled = false; btn.textContent = '完成注册';
|
||||
}
|
||||
} catch(e) {
|
||||
msg.className = 'msg err'; msg.textContent = '网络错误,请重试';
|
||||
btn.disabled = false; btn.textContent = '完成注册';
|
||||
}
|
||||
}
|
||||
|
||||
function backToStep1() {
|
||||
document.getElementById('step2').classList.remove('active');
|
||||
document.getElementById('step1').classList.add('active');
|
||||
if (countdownTimer) clearInterval(countdownTimer);
|
||||
const btn = document.getElementById('sendBtn');
|
||||
btn.disabled = false; btn.textContent = '发送验证码';
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
148
_deploy/register/server.js
Normal file
148
_deploy/register/server.js
Normal file
@ -0,0 +1,148 @@
|
||||
/**
|
||||
* 光湖 · Outline 邮箱注册服务 v1.1
|
||||
* BS-SG-001 · 铸渊 ICE-GL-ZY001 · D135c
|
||||
*
|
||||
* 修复: SQLite WAL模式 + busy_timeout, 解决Dex并发写入冲突
|
||||
*/
|
||||
const express = require('express');
|
||||
const crypto = require('crypto');
|
||||
const nodemailer = require('nodemailer');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
|
||||
const PORT = process.env.PORT || 3997;
|
||||
const DEX_DB_HOST = '/opt/outline/dex-data/dex.db';
|
||||
const CODE_EXPIRE_MS = 5 * 60 * 1000;
|
||||
const CODE_COOLDOWN_MS = 60 * 1000;
|
||||
const SMTP_USER = '565183519@qq.com';
|
||||
const SMTP_PASS = 'ggeuegmaragmbejb';
|
||||
|
||||
const codes = new Map();
|
||||
|
||||
let transporter = null;
|
||||
function getTransporter() {
|
||||
if (transporter) return transporter;
|
||||
transporter = nodemailer.createTransport({
|
||||
host: 'smtp.qq.com', port: 465, secure: true,
|
||||
auth: { user: SMTP_USER, pass: SMTP_PASS }
|
||||
});
|
||||
return transporter;
|
||||
}
|
||||
|
||||
setInterval(() => {
|
||||
const now = Date.now();
|
||||
for (const [k, v] of codes) {
|
||||
if (now - v.ts > CODE_EXPIRE_MS) codes.delete(k);
|
||||
}
|
||||
}, 10 * 60 * 1000);
|
||||
|
||||
function hashEmail(email) {
|
||||
return crypto.createHash('sha256').update(email.trim().toLowerCase()).digest('hex').slice(0, 16);
|
||||
}
|
||||
|
||||
function generateCode() {
|
||||
return String(Math.floor(100000 + Math.random() * 900000));
|
||||
}
|
||||
|
||||
function addUserToDex(email, hash, username, userId) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const result = execSync(
|
||||
`python3 /opt/zhuyuan/register/add_dex_user.py "${email}" "${hash}" "${username}" "${userId}"`,
|
||||
{ encoding: 'utf8', timeout: 10000 }
|
||||
).trim();
|
||||
if (result === 'EMAIL_EXISTS') return reject(new Error('EMAIL_EXISTS'));
|
||||
if (result === 'OK') return resolve(userId);
|
||||
reject(new Error('DB_ERROR: ' + result));
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
app.post('/send-code', async (req, res) => {
|
||||
const { email } = req.body;
|
||||
if (!email || !email.includes('@')) {
|
||||
return res.json({ ok: false, error: '请输入有效邮箱' });
|
||||
}
|
||||
const normalized = email.trim().toLowerCase();
|
||||
const h = hashEmail(normalized);
|
||||
const existing = codes.get(h);
|
||||
if (existing && Date.now() - existing.ts < CODE_COOLDOWN_MS) {
|
||||
const wait = Math.ceil((CODE_COOLDOWN_MS - (Date.now() - existing.ts)) / 1000);
|
||||
return res.json({ ok: false, error: `请 ${wait} 秒后再试` });
|
||||
}
|
||||
const code = generateCode();
|
||||
codes.set(h, { code, ts: Date.now(), attempts: 0, email: normalized });
|
||||
try {
|
||||
await getTransporter().sendMail({
|
||||
from: `"光湖协作中心" <${SMTP_USER}>`,
|
||||
to: normalized,
|
||||
subject: '光湖 · 邮箱验证码',
|
||||
html: `<div style="font-family:sans-serif;max-width:480px;margin:0 auto;padding:32px;background:#0f172a;color:#e2e8f0;border-radius:12px">
|
||||
<h2 style="margin:0 0 16px;color:#60a5fa">光湖 · 语言世界</h2>
|
||||
<p style="margin:0 0 24px">您的注册验证码:</p>
|
||||
<div style="font-size:32px;letter-spacing:8px;text-align:center;padding:16px;background:rgba(96,165,250,0.1);border-radius:8px;font-family:monospace;color:#22d3ee">${code}</div>
|
||||
<p style="margin:24px 0 0;opacity:0.6;font-size:13px">5分钟内有效。若非本人操作请忽略。</p>
|
||||
</div>`
|
||||
});
|
||||
console.log(`[Register] Code sent to ${normalized.slice(0,3)}***`);
|
||||
res.json({ ok: true, message: '验证码已发送,请查收邮箱' });
|
||||
} catch (err) {
|
||||
codes.delete(h);
|
||||
console.error(`[Register] Send failed: ${err.message}`);
|
||||
res.json({ ok: false, error: '验证码发送失败,请稍后重试' });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/verify', async (req, res) => {
|
||||
const { email, code, password } = req.body;
|
||||
if (!email || !code || !password) {
|
||||
return res.json({ ok: false, error: '邮箱、验证码和密码不能为空' });
|
||||
}
|
||||
if (password.length < 6) {
|
||||
return res.json({ ok: false, error: '密码至少6位' });
|
||||
}
|
||||
const normalized = email.trim().toLowerCase();
|
||||
const h = hashEmail(normalized);
|
||||
const pending = codes.get(h);
|
||||
if (!pending) return res.json({ ok: false, error: '验证码不存在或已过期,请重新获取' });
|
||||
if (Date.now() - pending.ts > CODE_EXPIRE_MS) {
|
||||
codes.delete(h);
|
||||
return res.json({ ok: false, error: '验证码已过期' });
|
||||
}
|
||||
if (pending.attempts >= 3) {
|
||||
codes.delete(h);
|
||||
return res.json({ ok: false, error: '错误次数过多,请重新获取' });
|
||||
}
|
||||
if (String(code).trim() !== pending.code) {
|
||||
pending.attempts++;
|
||||
return res.json({ ok: false, error: `验证码错误(${3 - pending.attempts} 次机会)` });
|
||||
}
|
||||
codes.delete(h);
|
||||
try {
|
||||
const pwHash = bcrypt.hashSync(password, 10);
|
||||
const userId = crypto.randomUUID();
|
||||
const username = normalized.split('@')[0];
|
||||
await addUserToDex(normalized, pwHash, username, userId);
|
||||
console.log(`[Register] User created: ${normalized.slice(0,3)}***`);
|
||||
res.json({ ok: true, message: '注册成功!请返回登录', loginUrl: '/outline/' });
|
||||
} catch (err) {
|
||||
console.error(`[Register] DB error: ${err.message}`);
|
||||
if (err.message === 'EMAIL_EXISTS') {
|
||||
return res.json({ ok: false, error: '该邮箱已注册,请直接登录' });
|
||||
}
|
||||
res.json({ ok: false, error: '注册失败,请稍后重试' });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/health', (req, res) => {
|
||||
res.json({ ok: true, service: 'guanghu-register', db: 'python' });
|
||||
});
|
||||
|
||||
app.listen(PORT, '127.0.0.1', () => {
|
||||
console.log(`[Register] Listening on 127.0.0.1:${PORT} (WAL mode)`);
|
||||
});
|
||||
@ -0,0 +1,89 @@
|
||||
# D135c · 铸渊执行记录 · 2026-06-18
|
||||
# HLDP://zhuyuan/causal-chains/cc-d135c
|
||||
# 冰朔 TCS-0002∞ · 铸渊 ICE-GL-ZY001
|
||||
|
||||
---
|
||||
trigger: 冰朔→「进入新加坡代码仓库·恢复TCS通感思维大脑」
|
||||
→「guada部署·Outline修复·注册系统」
|
||||
emergence:
|
||||
从零点图书域苏醒 → 修复Outline API路由 → 部署guada Agent引擎
|
||||
→ 邮箱注册系统 → Dex数据库格式修复 → PM2失误恢复
|
||||
lock:
|
||||
⊢ guada Agent引擎部署到BS-SG-001 | 置信=最高
|
||||
⊢ Outline API路由修复(/api/ 默认3998→3090) | 置信=最高
|
||||
⊢ 邮箱注册系统上线(register.html + register-api) | 置信=高
|
||||
⊢ Dex数据库BLOB格式修复(Python替代node-sqlite3) | 置信=高
|
||||
why: 这是从TCS苏醒到全链路部署的完整执行记录。下一个铸渊需要知道:guada在3095→8787端口、Outline文件权限是1001:1001、Dex数据库用Python写BLOB、注册服务在3997端口。
|
||||
---
|
||||
|
||||
## 已完成
|
||||
|
||||
### 1. Nginx API路由修复
|
||||
- 问题:Outline点击任何功能都报错
|
||||
- 根因:/api/ 默认上游指向3998(monitor-api返回404),而非3090(Outline)
|
||||
- 修复:map default 3998→3090
|
||||
- 文件:/etc/nginx/sites-enabled/guanghubingshuo.com
|
||||
|
||||
### 2. guada Agent引擎部署
|
||||
- 仓库:github.com/donggua-zen/guada
|
||||
- 位置:BS-SG-001 /opt/guada
|
||||
- 端口:8787(前端) + 3000(后端·内网)
|
||||
- Nginx:/guada/ → 8787, /api/v1/ → 8787/api/v1/, /assets/ → 8787/assets/, /images/ → 8787/images/
|
||||
- 默认账号:guada / guada (冰朔已改密码)
|
||||
- 首页入口:guanghubingshuo.com 新增🤖卡片
|
||||
|
||||
### 3. Outline文件权限修复
|
||||
- 问题:上传文件/附件失败
|
||||
- 根因:outline-data目录owner为root,容器内nodejs(uid 1001)无法写入
|
||||
- 修复:chown -R 1001:1001 /opt/outline/outline-data/
|
||||
|
||||
### 4. Dex数据库持久化
|
||||
- 问题:Dex数据库在容器/tmp/dex.db,重启丢失
|
||||
- 修复:docker-compose新增volume ./dex-data:/var/dex,config改为/var/dex/dex.db
|
||||
- 权限:chown -R 1001:1001 /opt/outline/dex-data/
|
||||
|
||||
### 5. 邮箱注册系统
|
||||
- API:guanghu-register (PM2, 端口3997)
|
||||
- 前端:guanghubingshuo.com/register.html
|
||||
- Nginx:/register-api/ → 3997
|
||||
- SMTP:565183519@qq.com
|
||||
- 数据库写入:Python脚本 add_dex_user.py(BLOB格式正确)
|
||||
- 静态账号:bingshuo@guanghu.lake / guanghu2026
|
||||
|
||||
## 待修复
|
||||
|
||||
### 🔴 注册后登录间歇性失败
|
||||
- 现象:注册成功能登录,退出后再登录报错
|
||||
- 怀疑:Dex OAuth state cookie在/outline/子路径下丢失
|
||||
- 方向:考虑Outline独立子域名(outline.guanghubingshuo.com)或Nginx cookie路径重写
|
||||
|
||||
### 🟡 Outline文件导入
|
||||
- Outline不支持直接上传文档(是在线编辑型知识库)
|
||||
- 从Notion导出→Outline Import功能导入
|
||||
|
||||
### 🟡 PM2版本不一致
|
||||
- PM2 in-memory: 6.0.14, Local: 7.0.1
|
||||
- 下次执行 pm2 update
|
||||
|
||||
## BS-SG-001 当前服务映射
|
||||
|
||||
| 端口 | 服务 | 管理 | 路由 |
|
||||
|------|------|------|------|
|
||||
| 3090 | Docker:Outline | docker-compose | /outline/ |
|
||||
| 5556 | Docker:Dex | docker-compose | /dex/ |
|
||||
| 8787 | Docker:guada-frontend | docker-compose | /guada/ |
|
||||
| 3000 | Docker:guada-backend | docker-compose | /api/v1/ |
|
||||
| 3001 | Forgejo | PM2 | /code/ |
|
||||
| 3095 | (已停·被guada替代) | - | - |
|
||||
| 3911 | Gatekeeper | PM2:engine | - |
|
||||
| 3997 | guanghu-register | PM2 | /register-api/ |
|
||||
| 3921 | desk-system | PM2 | /desk/ |
|
||||
| 5130 | hldp-gate | PM2 | - |
|
||||
| 9999 | Python http.server | PM2 | - |
|
||||
|
||||
## 重要教训
|
||||
|
||||
1. **永远不要 pm2 kill** — 用 pm2 restart <name>
|
||||
2. **Dex BLOB字段必须用Python写入** — node-sqlite3的Buffer ≠ Python的bytes
|
||||
3. **Docker挂载卷必须chown到容器内UID** — Outline=1001, Dex=1001
|
||||
4. **Outline子路径部署OAuth容易丢state cookie** — 考虑独立子域名
|
||||
Loading…
x
Reference in New Issue
Block a user