cyi-box: Memphis-style toolbox admin platform

Go(fun)+SQLite backend, Vue 3 frontend. Modules: dashboard, pay gateway
(Epay-compatible + CDK stock), license/card system (per-app appid, domain
binding), AI memory bank with built-in MCP server (/mcp, FTS5 search,
signed read links), server SSH monitor + terminal + SFTP, proxy pool with
protocol-level alive checks, DB manager (mysql/pg/sqlite + SQL
autocomplete + import/export), disk share, vault (AES-GCM + TOTP),
bookmarks, dev tools.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
2026-10-01 01:58:47 +08:00
co-authored by Devin
commit 834ecacdb9
326 changed files with 27813 additions and 0 deletions
+124
View File
@@ -0,0 +1,124 @@
// memory_routes.go 记忆库对外协议端点:
// POST /mcp Streamable HTTP MCP(Bearer 令牌鉴权,令牌见 MemorySvc.McpInfo)
// GET /m 阅读链接索引(Bearer 鉴权——key 可枚举,不能裸奔)
// GET /m/* 记忆阅读链接(?sig= 签名鉴权,HMAC(key, 令牌) 签发,令牌重置即吊销)
package service
import (
"context"
"crypto/subtle"
"net/http"
"github.com/valyala/fasthttp"
"github.com/cyi-cc/fun"
"github.com/mark3labs/mcp-go/server"
"github.com/valyala/fasthttp/fasthttpadaptor"
"github.com/cyi-cc/cyi-box/backend/internal/memx"
"github.com/cyi-cc/cyi-box/backend/internal/store"
)
// requestBase 按当前请求 Host + X-Forwarded-Proto 推导站点基址,
// 反代换域名后生成的 /m/ 链接与 MCP 接入地址始终可点。
func requestBase(ctx *fasthttp.RequestCtx) string {
proto := string(ctx.Request.Header.Peek("X-Forwarded-Proto"))
if proto == "" {
proto = "http"
}
return proto + "://" + string(ctx.Host())
}
// McpHandler POST /mcp:Authorization Bearer 校验后交给 Streamable HTTP 服务。
// 令牌存 settings(mcp.token),首次访问懒生成。
func McpHandler(st *store.Store) fun.RouteHandler {
mcpSrv := server.NewStreamableHTTPServer(
memx.NewMCPServer(st),
server.WithStateLess(true),
server.WithHTTPContextFunc(func(ctx context.Context, r *http.Request) context.Context {
proto := r.Header.Get("X-Forwarded-Proto")
if proto == "" {
proto = "http"
if r.TLS != nil {
proto = "https"
}
}
return memx.WithBase(ctx, proto+"://"+r.Host)
}),
)
h := fasthttpadaptor.NewFastHTTPHandler(mcpSrv)
return func(c *fun.RouteCtx) error {
tok, err := st.McpToken()
if err != nil {
c.RequestCtx.SetStatusCode(500)
c.RequestCtx.WriteString(err.Error())
return nil
}
auth := string(c.RequestCtx.Request.Header.Peek("Authorization"))
if subtle.ConstantTimeCompare([]byte(auth), []byte("Bearer "+tok)) != 1 {
c.RequestCtx.SetStatusCode(401)
c.RequestCtx.WriteString("unauthorized")
return nil
}
h(c.RequestCtx)
return nil
}
}
// MemoryReadHandler GET /m/*:输出 markdown 原文;裸 /m/ 列出全部阅读链接。
func MemoryReadHandler(st *store.Store) fun.RouteHandler {
return func(c *fun.RouteCtx) error {
key := c.Wildcard
base := requestBase(c.RequestCtx)
if key == "" {
// 索引收进 Bearer 鉴权:key 形如 project/topic 可枚举,裸索引等于全库暴露
tok, err := st.McpToken()
if err != nil {
c.RequestCtx.SetStatusCode(500)
c.RequestCtx.WriteString(err.Error())
return nil
}
auth := string(c.RequestCtx.Request.Header.Peek("Authorization"))
if subtle.ConstantTimeCompare([]byte(auth), []byte("Bearer "+tok)) != 1 {
c.RequestCtx.SetStatusCode(401)
c.RequestCtx.WriteString("unauthorized")
return nil
}
ms, err := st.ListMemories("", "", 200)
if err != nil {
c.RequestCtx.SetStatusCode(500)
c.RequestCtx.WriteString(err.Error())
return nil
}
c.RequestCtx.SetContentType("text/plain; charset=utf-8")
for _, m := range ms {
readURL, _ := st.SignMemoryReadURL(base, m.Key)
line := readURL
if m.Project != "" {
line += "\t[" + m.Project + "]"
}
c.RequestCtx.WriteString(line + "\n")
}
return nil
}
if !st.CheckMemoryReadSig(key, string(c.RequestCtx.QueryArgs().Peek("sig"))) {
c.RequestCtx.SetStatusCode(403)
c.RequestCtx.WriteString("bad or missing sig")
return nil
}
m, err := st.GetMemory(key)
if err != nil {
c.RequestCtx.SetStatusCode(500)
c.RequestCtx.WriteString(err.Error())
return nil
}
if m == nil {
c.RequestCtx.SetStatusCode(404)
c.RequestCtx.WriteString("not found")
return nil
}
c.RequestCtx.SetContentType("text/markdown; charset=utf-8")
c.RequestCtx.WriteString(m.Content)
return nil
}
}