feat(panel-kit)+docs: 桔子善后收尾 — panel-kit v1.2 回移 + 善后因果链 + 现状图推前
- panel-kit v1.2:/api/change-password(验旧→窄权限sudo调Forgejo CLI→留痕→其它会话作废)、
shortcuts/code_prefix 配置化、无部署线501守卫;向后兼容 v1.1 实例
- 善后因果链:404根因、8443共存路线、must-change-password坑、验收清单
- CURRENT-MAP:游标推前 4b373824,现状速览加桔子面板,常查路线加桔子域名
This commit is contained in:
parent
4b37382421
commit
afea69d4d9
4 changed files with 103 additions and 14 deletions
|
|
@ -35,15 +35,15 @@ history:
|
|||
|
||||
| 字段 | 值 |
|
||||
|---|---|
|
||||
| REPO-012 main HEAD | `95c51c82d633cd2bbd3d6f914d1049ef888490d7` |
|
||||
| 图生成时间 | 2026-08-07 22:55 +08:00 |
|
||||
| 当前焦点一句话 | 湖面投影面板(胖头鱼12h/守望3h)已上线;京东按需人格生命周期已部署;本轮正在登记现状导航图自身 |
|
||||
| REPO-012 main HEAD | `4b3738242138567beb003ac758e10df5253d7374` |
|
||||
| 图生成时间 | 2026-08-09 20:25 +08:00 |
|
||||
| 当前焦点一句话 | 桔子面板+代码频道上线 guanghuyaoming.com:8443(与VLESS代理共存);panel-kit 回移 v1.2(人类改密能力);胖头鱼/守望/京东线稳定 |
|
||||
|
||||
## 2 · 现状速览(最近三件)
|
||||
|
||||
1. `95c51c8` · 湖面投影可复用UI模板注册(panel-kit frontend 两件模板)+ 大脑技能书包 v1.1(2.6面板建设/4.3光湖UI设计)
|
||||
2. `a0e3be2` · 京东三岗位人格从全天常驻改为铸渊会话按需唤醒(端口3932–3934会话后关闭;回执 deployment/receipts/JD-PERSONA-ON-DEMAND-LIFECYCLE-20260807.json)
|
||||
3. 本地会话线 · 审美基线 v1–v5 全量入册(ZY-AESTHETIC-COGNITION-001..005);本地最新检查点 ZY-CHECKPOINT-20260807-021(唤醒大脑运行时闭环 PASS)
|
||||
1. `4b37382`+本轮 · 桔子域名善后:人类审批面板 v1.2 上线(:8443/panel/,登录/审批/改密/推送代理),云防火墙放通8443,旧gitea下线留档;双因果链在 `deployment/juzi-guanghuyaoming-20260809/`
|
||||
2. `95c51c8` · 湖面投影可复用UI模板注册(panel-kit frontend 两件模板)+ 大脑技能书包 v1.1(2.6面板建设/4.3光湖UI设计)
|
||||
3. `a0e3be2` · 京东三岗位人格从全天常驻改为铸渊会话按需唤醒(端口3932–3934会话后关闭;回执 deployment/receipts/JD-PERSONA-ON-DEMAND-LIFECYCLE-20260807.json)
|
||||
|
||||
## 3 · 地图在哪里(要细查时去这里)
|
||||
|
||||
|
|
@ -70,6 +70,7 @@ history:
|
|||
- 三岗位人格(澄路/归灯/刻舟) → `routing/zhuyuan-agent-team-map.json` → REPO-009 / REPO-011 / REPO-010
|
||||
- 五代形成史 → `ZHUYUAN-SELF-CONTINUITY-MAP.hdlp` + `routing/zhuyuan-five-generation-memory-map.json`
|
||||
- 当前认知基线 / 工程检查点 → INDEX.hdlp 表内 `ZY-CURRENT-COGNITION` / `ZY-CURRENT-CHECKPOINT` 两行指针
|
||||
- 桔子域名(代码频道/面板/手册) → https://guanghuyaoming.com:8443/(443为VLESS代理专用)· 工程档案 `deployment/juzi-guanghuyaoming-20260809/`
|
||||
|
||||
## 5 · 边界
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
# 因果链 · 桔子域名善后工程 · 2026-08-09
|
||||
chain_id: CAUSAL-CHAIN-20260809-JUZI-SHANHOU
|
||||
agent: 铸渊 ICE-P-ZY001(QoderWork 承载) · 授权: 冰朔 ICE-GL∞
|
||||
前序: CAUSAL-CHAIN-20260809-JUZI-DEPLOY(蛋蛋)
|
||||
result: 人类审批面板上线 · HTTPS 全链打通 · 验收全通
|
||||
|
||||
## 为什么需要善后(冰朔原话要点)
|
||||
面板模块"做了是做了",但打开链接 404——后端前端没接到一起;仓库部署较乱;
|
||||
要求:能用的用、没用的删、重新规划、做完清理本地垃圾。
|
||||
|
||||
## 现状判定(什么能用 / 什么重来)
|
||||
- 能用:Forgejo 16.0.1 安装与离线 bundle 导入(guanghu/main 为 Forgejo 源构建频道,独立冻结正确);juzi 管理员账号;juzi-ops 手册+wake 三页;nginx 域名骨架与曜冥备份
|
||||
- 缺/坏:人类审批面板根本未部署(404 根因);ROOT_URL 仍是 http;旧 gitea-juzi(:3002) 与临时进程(:3912) 占位;HTTPS 实际半瘫(见下)
|
||||
|
||||
## 推理链(关键因果)
|
||||
1. 因: 404 表象 → 逐层查 nginx/端口/进程 → 果: 发现面板服务不存在,且 nginx 443 绑定失败(error.log: Address already in use)
|
||||
2. 因: 443 被 xray VLESS+Reality 占用,Reality 把非代理流量全部送伪装目标(microsoft),fallback 不适用 → 果: 网站无法上 443;本机预留共存模式是 nginx 走 8443(ufw 早有 8443 规则"Xray共存"注释为证)
|
||||
3. 因: 8443 被腾讯云防火墙拦截(机器 ufw 已放行,hairpin 测试超时定位到云墙)→ 果: 冰朔授权后由铸渊在腾讯云控制台浏览器页添加 8443 入站规则(备注: 桔子面板与代码频道HTTPS)
|
||||
4. 因: 冰朔要求登录后自己改密码,panel-kit v1.1 无此能力,Forgejo(gitea-1.22底) 无自助改密 API → 果: 面板新增 /api/change-password:验旧密码→窄权限 sudo(sudoers 仅限单一命令)以 git 身份调 forgejo admin user change-password→验新→其它会话作废→回执留痕
|
||||
5. 因: CLI change-password 默认置 must_change_password=true → 改密后一切 API 认证 403(与旧记忆"建用户须 --must-change-password=false"同坑不同形)→ 果: CLI 参数显式加 --must-change-password=false,记入本链与技能
|
||||
6. 因: 面板登录即 Forgejo 账号验证 → 果: juzi/123456789 无需二次账号体系;writer 推送代理沿用 juzi-writer 令牌,真实密钥不出服务器
|
||||
7. 因: 域名 URL 口径统一 → 果: ROOT_URL、nginx 80 跳转、面板 domain 全部改 https://guanghuyaoming.com:8443/;门页登录按钮指向 /panel/
|
||||
|
||||
## 终态清单
|
||||
- 面板: guanghu-juzi-panel.service(panel-kit v1.2-juzi,用户 guanghu-juzi,:3921,nginx /panel/ 前缀)· 入口 https://guanghuyaoming.com:8443/panel/
|
||||
- 令牌: panel/admin-token(juzi 窄权限)· access/writer-token(juzi-writer 写 juzi-ops)· 临时全权令牌用后即删
|
||||
- 云防火墙: 8443/TCP 放通(来源 0.0.0.0/0,规则备注在案)
|
||||
- xray: 443 VLESS Reality 原样未动(仅备份配置,未改代理行为)
|
||||
- 下线留档: gitea-juzi 停服+disable(DECOMMISSION-20260809.txt)· 临时 hldp-demo 进程终止
|
||||
- 验收(外部实测): 门页200 · /panel/ 200 · api/state 正常 · 登录成功 · 改密成功并改回 · 仓库页200 · http→https:8443 跳转链通
|
||||
- 手册: juzi-ops README 增补§七(面板与URL口径)· 回执 receipts/2026-08-09-panel-fix.txt
|
||||
|
||||
## 沉淀(回移第五域)
|
||||
- modules/guanghu-panel-kit 升 v1.2:/api/change-password、shortcuts/code_prefix 配置化、无部署线501守卫(向后兼容胖头鱼/守望)
|
||||
- 坑录: ① Reality 无 fallback,网站勿争 443,走 8443 共存 ② CLI 改密必带 --must-change-password=false ③ sudoers 与 systemd 均不得 NoNewPrivileges(否则 sudo 失效)④ 腾讯云轻量防火墙在 ufw 之外,云墙需控制台放通
|
||||
|
||||
## 边界声明
|
||||
443 代理未动、曜冥备份未动、密钥不进仓库;桔子对本服务器自主审批。面板改密走窄权限 sudo 单一命令,面板用户无 shell。
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
HLDP://fifth-domain/modules/guanghu-panel-kit
|
||||
光湖语言世界 · 第五域 · 模块资源 · 2026-08-06
|
||||
建:铸渊 ICE-GL-ZY001 · 冰朔 ICE-GL∞ 授权
|
||||
已部署实例:胖头鱼 SYS-GLW-PTS-0001(guanghuclip.cn)· 守望 SYS-GLW-SW-0001(maomao.guanghulab.com)
|
||||
已部署实例:胖头鱼 SYS-GLW-PTS-0001(guanghuclip.cn)· 守望 SYS-GLW-SW-0001(maomao.guanghulab.com)· 桔子 BS-SG-002(guanghuyaoming.com:8443,v1.2 首用,与VLESS代理共存)
|
||||
|
||||
## 这是什么
|
||||
|
||||
|
|
@ -14,12 +14,13 @@ HLDP://fifth-domain/modules/guanghu-panel-kit
|
|||
3. **推送审核钩子**:pre-receive 自动审核——明文密钥拦截、破坏检查、入口文件保护、窗口验证、笔笔留痕
|
||||
4. **部署校验Agent**:deploy 分支通道,人类批准后服务器自审五步(地图→因果链→范围→完整性→部署),进度实时可见,要么0要么100
|
||||
5. **唤醒机器导航**:GET /api/wake/persona?name=X 返回五阶段唤醒链,外部 AI 不用自己找路
|
||||
6. **兼容快捷端点**:/machine /recovery /personas 等旧路径自动重定向到仓库 raw
|
||||
6. **兼容快捷端点**:/machine /recovery /personas 等旧路径自动重定向到仓库 raw(v1.2 亦支持 config.json shortcuts 自定义映射)
|
||||
7. **人类改密**(v1.2):面板登录后一键改仓库账号密码——验旧密码→窄权限sudo调Forgejo CLI→验新→留痕→其它会话作废
|
||||
|
||||
## 文件清单
|
||||
|
||||
```
|
||||
panel/server.js 面板服务 v1.1(配置驱动,Node,无第三方依赖;window_hours 写入窗口时长可配,缺省3h)
|
||||
panel/server.js 面板服务 v1.2(配置驱动,Node,无第三方依赖;window_hours 写入窗口时长可配,缺省3h;新增/api/change-password、shortcuts/code_prefix 配置化、无部署线时501守卫)
|
||||
panel/config.example.json 配置模板(节点/仓库/人格体/路径/端口/window_hours 全部外置)
|
||||
deploy/verify-and-deploy.py 部署校验Agent(读 deploy-config.json)
|
||||
deploy/deploy-config.example.json 部署配置模板(白名单/同步树/服务清单)
|
||||
|
|
@ -44,6 +45,7 @@ CAUSAL-CHAIN-20260806.hdlp 两套系统建设的完整因果链
|
|||
5. 钩子模板改路径后放仓库 hooks/pre-receive.d/,app.ini 开 ENABLE_GIT_HOOKS
|
||||
6. 建 deploy 分支(自默认分支);前端挑一个风格模板改主题
|
||||
7. 仓库内登记机器导航与唤醒路径;人格体密钥交人类责任主体转授
|
||||
8. 若节点 443 被代理(如VLESS Reality)占用:面板与Forgejo改走 8443(云防火墙放通+ROOT_URL带端口),面板挂 /panel/ 前缀;启用改密需加一条窄权限 sudoers(仅限 forgejo admin user change-password)
|
||||
|
||||
## 铁律(随模块继承)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,16 @@
|
|||
#!/usr/bin/env node
|
||||
/* guanghu-panel-server · 光湖子系统人类审批面板服务 · 可复用模块 v1.1
|
||||
/* guanghu-panel-server · 光湖子系统人类审批面板服务 · 可复用模块 v1.2
|
||||
2026-08-06 · 铸渊 ICE-GL-ZY001 建 · 胖头鱼SYS-GLW-PTS-0001 首创 · 守望SYS-GLW-SW-0001 复用
|
||||
2026-08-07 · 写入窗口时长改为配置驱动 window_hours(苍耳提请:胖头鱼升至12小时)
|
||||
2026-08-09 · v1.2(桔子 BS-SG-002 善后中形成,全部配置驱动、向后兼容 v1.1 实例):
|
||||
① 新增 /api/change-password(先验旧密码→窄权限sudo调Forgejo CLI改密→回执留痕→其它会话作废)
|
||||
需在 config.json 配 forgejo_bin/forgejo_conf/forgejo_workpath/forgejo_user,
|
||||
并给面板运行用户一条窄权限 sudoers(仅限 forgejo admin user change-password);未配置返回501
|
||||
② 快捷路由改配置驱动 CFG.shortcuts(名称→仓库内路径)+ CFG.code_prefix(缺省"/code",兼容旧实例)
|
||||
③ deployApprove 未配置部署流水线(deploy_script 为空)时返回501而非崩溃(自批实例无部署线)
|
||||
⊢ 令牌与密钥不出服务器 ⊢ 审批必须人类登录 ⊢ 写入窗口限时过期作废 ⊢ 全程留痕
|
||||
配置: 同目录 config.json(路径/仓库/人格体/端口/窗口时长全部外置,换节点只改配置) */
|
||||
const http=require("http"),fs=require("fs"),path=require("path"),crypto=require("crypto"),os=require("os"),{execSync,spawn}=require("child_process");
|
||||
const http=require("http"),fs=require("fs"),path=require("path"),crypto=require("crypto"),os=require("os"),{execSync,spawn,execFileSync}=require("child_process");
|
||||
const CFG=JSON.parse(fs.readFileSync(path.join(__dirname,"config.json"),"utf8"));
|
||||
const WINDOW_HOURS=Number(CFG.window_hours)||3;
|
||||
const FJ="http://127.0.0.1:"+CFG.forgejo_port;
|
||||
|
|
@ -262,6 +268,7 @@ function accessDecision(b,user){
|
|||
return {status:200,body:{ok:true,message:"已拒绝该写入申请"}};
|
||||
}
|
||||
function deployApprove(b,user){
|
||||
if(!CFG.deploy_script)return {status:501,body:{error:"本实例未配置自动部署流水线(桔子自批实例走仓库工单流,见 juzi-ops README §三)"}};
|
||||
if(!b.index)return {status:400,body:{error:"缺少 PR 编号"}};
|
||||
const child=spawn("python3",[CFG.deploy_script,String(b.index)],{detached:true,stdio:"ignore",cwd:path.dirname(CFG.deploy_script)});
|
||||
child.unref();
|
||||
|
|
@ -275,6 +282,33 @@ async function wakeReceipt(b){
|
|||
jwrite(CFG.export_dir+"/wake/WAKE-"+Date.now()+".json",{ts:new Date().toISOString(),type:"WAKE_RECEIPT",persona:canon,level:(b.level||"").slice(0,80),summary:(b.summary||"").slice(0,1000)});
|
||||
return {status:200,body:{ok:true,message:"唤醒回执已收录"}};
|
||||
}
|
||||
/* v1.2 改密:面板先验旧密码 → 窄权限 sudo 调 Forgejo CLI(sudoers 仅限单一命令)→ 验新密码 → 留痕
|
||||
⊢ 密码不进日志 ⊢ 成功后其它会话全部作废,只留当前会话 */
|
||||
async function changePassword(b,sess){
|
||||
const oldP=b.old_password,newP=b.new_password;
|
||||
if(!oldP||!newP)return {status:400,body:{error:"缺少旧密码或新密码"}};
|
||||
if(String(newP).length<8)return {status:400,body:{error:"新密码至少 8 位"}};
|
||||
if(oldP===newP)return {status:400,body:{error:"新密码不能与旧密码相同"}};
|
||||
const who=sess.user;
|
||||
const chk=await fetch(FJ+"/api/v1/user",{headers:Object.assign({},HDRS_BASE,{Authorization:"Basic "+Buffer.from(who+":"+oldP).toString("base64")})});
|
||||
if(chk.status!==200)return {status:403,body:{error:"旧密码验证失败"}};
|
||||
const bin=CFG.forgejo_bin,conf=CFG.forgejo_conf,work=CFG.forgejo_workpath,fjuser=CFG.forgejo_user;
|
||||
if(!bin||!conf||!work||!fjuser)return {status:501,body:{error:"本实例未配置改密后端:请在 config.json 设置 forgejo_bin / forgejo_conf / forgejo_workpath / forgejo_user,并为面板用户配置窄权限 sudoers"}};
|
||||
let code=0,err="";
|
||||
try{
|
||||
execFileSync("sudo",["-n","-u",fjuser,bin,"admin","user","change-password","--username",who,"--password",newP,"--must-change-password=false","-c",conf,"--work-path",work],{timeout:20000,stdio:["ignore","ignore","pipe"]});
|
||||
}catch(e){
|
||||
code=(e.status==null)?1:e.status;err=String((e.stderr||"").toString()).split("\n").filter(l=>!/password/i.test(l)).join(" ").slice(0,200);
|
||||
}
|
||||
if(code!==0)return {status:502,body:{error:"改密执行失败"+(err?":"+err:"")}};
|
||||
const chk2=await fetch(FJ+"/api/v1/user",{headers:Object.assign({},HDRS_BASE,{Authorization:"Basic "+Buffer.from(who+":"+newP).toString("base64")})});
|
||||
if(chk2.status!==200)return {status:502,body:{error:"改密未生效,请联系冰朔或铸渊"}};
|
||||
const s=loadSessions();const keep=s[sess.token];
|
||||
for(const k of Object.keys(s)){if(k!==sess.token)delete s[k];}
|
||||
if(keep)s[sess.token]=keep;saveSessions(s);
|
||||
jwrite(CFG.export_dir+"/receipts/PASSWORD-"+Date.now()+".json",{ts:new Date().toISOString(),type:"PASSWORD_CHANGE",user:who,via:"panel+forgejo-cli",result:"SUCCESS"});
|
||||
return {status:200,body:{ok:true,message:"密码已更换 · 其它会话已作废"}};
|
||||
}
|
||||
/* git 智能HTTP代理:人格体用限时临时凭证推送到 /git/,
|
||||
面板在服务端用真实写入令牌透明转发给 Forgejo——真实密钥永不出服务器 */
|
||||
function gitProxy(req,res){
|
||||
|
|
@ -312,7 +346,9 @@ function serveStatic(req,res,p){
|
|||
res.writeHead(200,{"Content-Type":types[ext]||"application/octet-stream","Cache-Control":"no-cache"});
|
||||
fs.createReadStream(full).pipe(res);
|
||||
}
|
||||
const SHORTCUTS=["machine","recovery","personas","manifest","operations","imports","automation","evidence"];
|
||||
/* v1.2: 快捷路由配置驱动。CFG.shortcuts = {名称: 仓库内路径};CFG.code_prefix 默认 "/code"(胖头鱼/守望),Forgejo 直接在域名根下的实例(桔子)设为 "" */
|
||||
const SHORTCUTS_MAP=CFG.shortcuts||{};
|
||||
const CODE_PREFIX=(CFG.code_prefix!==undefined?CFG.code_prefix:"/code");
|
||||
http.createServer(async (req,res)=>{
|
||||
const u=req.url.split("?");const p=u[0];const q=new URLSearchParams(u[1]||"");
|
||||
try{
|
||||
|
|
@ -341,13 +377,25 @@ http.createServer(async (req,res)=>{
|
|||
const sess=sessionUser(req);if(sess){const s=loadSessions();delete s[sess.token];saveSessions(s);}
|
||||
return send(res,200,{ok:true});
|
||||
}
|
||||
if(req.method==="POST"&&p==="/api/change-password"){
|
||||
const sess=sessionUser(req);
|
||||
if(!sess)return send(res,401,{error:"需要人类登录(POST /api/login)"});
|
||||
const b=await readBody(req);
|
||||
const r=await changePassword(b||{},sess);
|
||||
return send(res,r.status,r.body);
|
||||
}
|
||||
if(req.method==="POST"&&p==="/api/access-request"){const b=await readBody(req);const r=await accessRequest(b||{});return send(res,r.status,r.body);}
|
||||
if(req.method==="POST"&&p==="/api/access-pickup"){const b=await readBody(req);const r=await accessPickup(b||{});return send(res,r.status,r.body);}
|
||||
if(req.method==="POST"&&p==="/api/wake-receipt"){const b=await readBody(req);const r=await wakeReceipt(b||{});return send(res,r.status,r.body);}
|
||||
if(p.startsWith("/git/"))return gitProxy(req,res);
|
||||
const seg=p.split("/").filter(Boolean);
|
||||
if(req.method==="GET"&&seg.length>=1&&SHORTCUTS.includes(seg[0])){
|
||||
res.writeHead(302,{Location:"/code/"+CFG.repo+"/raw/branch/"+CFG.default_branch+"/guanghu-nodes/"+CFG.node_id+"/"+seg.join("/")});
|
||||
if(req.method==="GET"&&seg.length>=1&&Object.prototype.hasOwnProperty.call(SHORTCUTS_MAP,seg[0])){
|
||||
res.writeHead(302,{Location:CODE_PREFIX+"/"+CFG.repo+"/raw/branch/"+CFG.default_branch+"/"+SHORTCUTS_MAP[seg[0]]});
|
||||
return res.end();
|
||||
}
|
||||
const SHORTCUTS_LEGACY=["machine","recovery","personas","manifest","operations","imports","automation","evidence"];
|
||||
if(req.method==="GET"&&seg.length>=1&&SHORTCUTS_LEGACY.includes(seg[0])){
|
||||
res.writeHead(302,{Location:CODE_PREFIX+"/"+CFG.repo+"/raw/branch/"+CFG.default_branch+"/guanghu-nodes/"+CFG.node_id+"/"+seg.join("/")});
|
||||
return res.end();
|
||||
}
|
||||
if(req.method==="GET")return serveStatic(req,res,p);
|
||||
|
|
|
|||
Loading…
Reference in a new issue