rootwarren

    rootwarren: MoonBit full-stack Markdown wiki backend with protected REST API for LLM/client read and write

    moonbit
    wiki
    markdown
    full-stack
    ssr
    Download zip
    Version
    0.1.0
    License
    Apache-2.0
    Last updated
    28 days ago
    Downloads
    12

    #rootwarren

    一片面向人类与 Agent 的活文档森林。

    rootwarren 是一个由 MoonBit 构建的、文件型 Markdown 文档空间:Markdown 文件是生长出来的树干与枝条,目录是不同枝系,文档之间用链接根系相连。人类在其中阅读与编辑,Agent / LLM 通过受保护的 REST API 进入、检索与写入。

    • 基于 moonbit-community/rabbita 完整版(SSR + 前端水合)+ hackwaly/moonback(后端 API)构建,前后端共享同一份组件包。
    • content/ 下的 Markdown 文档是数据源(slug 为相对路径去掉 .md)。
    • 浏览器端是共享 full-rabbita 组件,由服务端 SSR 渲染、客户端水合。
    • 提供受保护的 REST API 供 LLM 与其他客户端读写;文档间的链接关系维护在 meta/links.json(反向台账),支持链入页面。

    请注意,该项目仍然在开发阶段中,请不要将其投入生产使用

    #架构

    app/ 共享组件包(js+native+wasm,纯 UI,数据走 @http API) cmd/browser/ 前端水合入口 @rabbita.new(@app.app).hydrate() cmd/server/ moonback 后端 + @rserver.Server(component=@app.app) SSR ├─ main.mbt 路由与 API └─ auth.mbt 认证状态(JWT + cookie session) public/ 静态资源(index.js 由 warren 生成,勿提交)

    • 用户/文档/配置存在 meta/(gitignored),密钥与密码只存哈希。
    • 认证用 HttpOnly cookie session(登录后浏览器自动携带,写接口无需手动带 header)。

    #开发

    本机 MoonBit nightly 的 native archiver 探测有误,需 MOON_CC=clang(Makefile 已导出)。make 仅方便快速启动和开发调试,其中 make dev 依赖 warren 进行热更新

    make check # 类型检查全部 target make dev # 自动准备 .env + 起 warren 全栈 dev server(端口 4300)

    访问 http://127.0.0.1:4300/

    开发环境登录make dev 会在 .env 缺失时自动生成一个(含随机 ADMIN_TOKEN),并在终端打印一次登录凭据。之后登录后台:

    用户名: operator 密码: <make dev 自动生成的随机值,见 .env 的 ADMIN_TOKEN>

    make dev 是自动生成(测试/开发即开即用);生产/正式请用 make run,它要求先手动配置 .env,缺失时会给出指引并退回,不自动生成。

    #构建 / 测试

    make build # native server + frontend js make test # 运行测试(moon test) make init-env # 生成 .env 模板(不覆盖已有) moon fmt # 格式化

    #测试 / CI

    • moon test 运行根库纯函数测试(front matter 解析、wikilink 重写、站点配置回退)。
    • 仓库含 GitHub Actions(.github/workflows/ci.yml):moon check + moon test
    • 本机 native 构建需 MOON_CC=clang(Makefile 已导出)。

    #发布(mooncakes.io)

    项目以 moon publish 发布到 mooncakes.io。发布前:

    1. moon.mod 填入公开 GitHub 仓库地址(repository),并确保仓库公开(mooncakes 需要借它打包/展示文档与 README)。
    2. moon checkmoon testmake build 全部通过。
    3. 本地 moon publish 并按 mooncakes 提示登录授权。

    注意:meta/public/index.js.env 等本地/生成物已 gitignore,不会发布。

    #开源与许可

    本项目采用 Apache-2.0 许可,代码与 CSS 样式均为独立实现

    部分功能呈现效果参考:

    #登录与配置

    后端登录凭据由 .env 提供(ADMIN_TOKEN=初始密码,用户名固定 operator)。.env 是 gitignored 的本地配置。

    • 开发/测试:运行 make dev,若 .env 不存在会自动生成(随机强密码),首次生成会在终端打印登录凭据;.env 已存在则复用、不覆盖。
    • 生产/正式:运行 make run,若 .env 缺失会打印配置指引并退出。请先手动配置:

    cp .env.example .env # 编辑 .env,把 ADMIN_TOKEN 设成强密码 openssl rand -hex 16 # 可用于生成强密码 make run

    配置说明:

    含义
    ADMIN_TOKEN后台初始登录密码(用户名固定 operator),登录后可在后台改为持久密钥
    MBT_MDWIKI_IPmake run 监听地址
    PORTmake run 端口(warren dev 固定用 4300)

    环境变量优先于 .env:若在 shell 已设置同名变量,则 .env 不覆盖它。

    #能力

    • 阅读/d/{slug} 服务端渲染阅读页(面包屑 + 文档树 + tags + 产品名 title);/ 是 full-rabbita SPA(SSR + 水合)。
    • 类型化内容(Typecho 式):文档可设 status(public/private/hidden/draft)与 category;分类聚合 + /category/{slug} 分类页;/posts/{slug} 固定链接;/feed RSS 2.0 订阅。
    • 文档链接关系:正文相对链接自动重写为 /d/{slug}meta/links.json 维护反向链接台账(目标 -> 引用它的文档),保存/删除文档自动更新;SSR 阅读页展示“链入页面”,并提供 GET /api/v1/backlinks/{slug} 查询。
    • 认证:API key(Bearer,可绑定用户 + 读/写 slug 前缀范围)或 cookie session(浏览器自动携带)。
    • 权限分层superadmin > admin > write > read > none;admin 不能管理 superadmin。
    • 站点设置:产品名、公开/私有(site.public)、文档树、llms.txt 三档策略(public/partial/disabled + 前缀过滤)。
    • 后台管理:用户 CRUD + 密码重置 + 启停;API key 创建/吊销 + 范围绑定;站点设置;分类查看。
    • 安全:slug 路径穿越防护(.././绝对路径拒绝)、API key 范围过滤、越权禁止。

    #API

    docs/API.md(REST 契约)。服务端点包括:

    GET /health GET /d/*slug 服务端渲染阅读页 GET /llms.txt GET /feed RSS 2.0 订阅 GET /category/*slug 分类页(SSR) GET /posts/*slug 文章固定链接(SSR) GET /api/v1/categories 分类列表 + 计数 GET /api/v1/config GET /api/v1/docs GET /api/v1/docs/*slug PUT /api/v1/docs/*slug (需写权限 + 前缀范围) DELETE /api/v1/docs/*slug (需写权限 + 前缀范围) POST /api/auth/login 返回 {token,role} + 设 mbt_auth cookie POST /api/auth/logout GET /api/auth/me GET /api/admin/users (仅管理员) POST /api/admin/users (仅管理员) POST /api/admin/users/update POST /api/admin/users/password GET /api/admin/keys (仅管理员) POST /api/admin/keys POST /api/admin/keys/revoke GET /api/admin/site (仅管理员) POST /api/admin/site

    Storage

    pub trait Storage {
    async fn read(Self, slug : String) -> String?
    async fn write(Self, slug : String, content : String) -> Unit
    async fn delete(Self, slug : String) -> Unit
    async fn list(Self) -> Array[String]
    }

    内容存储抽象:MVP 只实现本地文件,后续可加内存盘(测试)/对象存储。

    EntropyUnavailable

    pub(all) suberror EntropyUnavailable {
    EntropyUnavailable
    }

    StorageError

    pub(all) suberror StorageError {
    InvalidSlug(String)
    }

    存储层错误

    AdminTokenProvider

    pub struct AdminTokenProvider {
    username : String
    key_hash : String
    }

    AdminTokenProvider::configured

    fn AdminTokenProvider::configured(self : AdminTokenProvider) -> Bool

    AdminTokenProvider::from_hash

    fn AdminTokenProvider::from_hash(username : String, key_hash : String) -> AdminTokenProvider

    AdminTokenProvider::new

    fn AdminTokenProvider::new(username : String, token : String) -> AdminTokenProvider

    AdminTokenProvider::set_credentials

    fn AdminTokenProvider::set_credentials(self : AdminTokenProvider, username : String, key : String) -> Unit

    AdminTokenProvider::username

    fn AdminTokenProvider::username(self : AdminTokenProvider) -> String

    AdminTokenProvider::verify

    fn AdminTokenProvider::verify(self : AdminTokenProvider, username : String, password : String) -> Bool

    当前本地账户 provider 的公开入口;未来 provider 可替换这一层。

    ApiKeyInfo

    pub struct ApiKeyInfo {
    id : Int
    name : String
    scopes : String
    user_id : Int
    read_prefix : String
    write_prefix : String
    enabled : Bool
    created_at : String
    }

    ApiKeyRecord

    pub struct ApiKeyRecord {
    id : Int
    name : String
    key_hash : String
    scopes : String
    user_id : Int
    read_prefix : String
    write_prefix : String
    enabled : Bool
    created_at : String
    }

    CategoryRecord

    pub struct CategoryRecord {
    id : Int
    name : String
    slug : String
    parent_id : Int
    description : String
    }

    DocumentMeta

    pub struct DocumentMeta {
    body : String
    title : String
    tags : String
    status : String
    category : String
    }

    JsonMeta

    type JsonMeta

    元数据存储:纯文本 JSON 文件(meta/config.json + meta/api_keys.json)。 不用 sqlite:减少依赖、文件可 git、可回退;单进程规模足够。 写文件用「临时文件 + rename」保证原子性。
    async fn JsonMeta::backlinks(self : JsonMeta, target : String) -> Array[String]

    查询某 target 的链入页面(引用它的文档 slug 列表)。

    JsonMeta::create_api_key

    async fn JsonMeta::create_api_key(self : JsonMeta, name : String, scopes : String, user_id : Int, read_prefix : String, write_prefix : String) -> String

    创建 API key:返回明文 key(仅此一次,文件里只存哈希)

    JsonMeta::create_category

    async fn JsonMeta::create_category(self : JsonMeta, name : String, slug : String, parent_id : Int, description : String) -> Bool

    JsonMeta::create_user

    async fn JsonMeta::create_user(self : JsonMeta, username : String, password : String, role : String, write_prefix : String) -> Bool

    JsonMeta::find_user

    async fn JsonMeta::find_user(self : JsonMeta, username : String) -> UserRecord?

    JsonMeta::find_user_by_id

    async fn JsonMeta::find_user_by_id(self : JsonMeta, id : Int) -> UserRecord?

    JsonMeta::get_config

    async fn JsonMeta::get_config(self : JsonMeta, key : String) -> String?

    读配置项,不存在返回 None

    JsonMeta::list_api_keys

    async fn JsonMeta::list_api_keys(self : JsonMeta) -> Array[ApiKeyInfo]

    列出全部 API key(不含哈希)

    JsonMeta::list_categories

    async fn JsonMeta::list_categories(self : JsonMeta) -> Array[CategoryRecord]

    JsonMeta::list_users

    async fn JsonMeta::list_users(self : JsonMeta) -> Array[UserRecord]

    JsonMeta::open

    async fn JsonMeta::open(dir : String) -> JsonMeta

    打开元数据目录(不存在则自动创建)
    async fn JsonMeta::record_links(self : JsonMeta, source : String, targets : Array[String]) -> Unit

    把一个源文档「引用的目标列表」写进台账。 先移除该源文档的旧记录(避免残留),再写入新的 target -> source 反向映射。
    async fn JsonMeta::remove_source_links(self : JsonMeta, source : String) -> Unit

    移除一个源文档在台账中的所有记录(删除文档时调用)。

    JsonMeta::reset_user_password

    async fn JsonMeta::reset_user_password(self : JsonMeta, id : Int, password : String) -> Bool

    JsonMeta::revoke_api_key

    async fn JsonMeta::revoke_api_key(self : JsonMeta, id : Int) -> Unit

    吊销 API key(软删除:enabled = false)

    JsonMeta::set_config

    async fn JsonMeta::set_config(self : JsonMeta, key : String, value : String) -> Unit

    写配置项

    JsonMeta::update_user

    async fn JsonMeta::update_user(self : JsonMeta, id : Int, role : String, write_prefix : String, enabled : Bool) -> Bool

    JsonMeta::verify_api_key

    async fn JsonMeta::verify_api_key(self : JsonMeta, key : String) -> ApiKeyRecord?

    校验 API key:有效时返回完整绑定记录。

    JsonMeta::verify_user

    async fn JsonMeta::verify_user(self : JsonMeta, username : String, password : String) -> UserRecord?

    LocalStorage

    type LocalStorage

    本地文件系统实现:文档映射到 root/slug.md

    LocalStorage::delete

    async fn LocalStorage::delete(self : LocalStorage, slug : String) -> Unit

    LocalStorage::list

    async fn LocalStorage::list(self : LocalStorage) -> Array[String]

    LocalStorage::new

    fn LocalStorage::new(root : String) -> LocalStorage

    LocalStorage::read

    async fn LocalStorage::read(self : LocalStorage, slug : String) -> String?

    LocalStorage::write

    async fn LocalStorage::write(self : LocalStorage, slug : String, content : String) -> Unit

    UserRecord

    pub struct UserRecord {
    id : Int
    username : String
    password_hash : String
    role : String
    enabled : Bool
    write_prefix : String
    }

    config_site_title

    fn config_site_title(value : String?) -> String

    document_meta

    fn document_meta(raw : String) -> DocumentMeta

    解析 Markdown front matter(title / tags / status / category),返回结构体。
    fn extract_link_slugs(html : String) -> Array[String]

    从已重写的渲染 HTML 中提取所有指向站点文档的 slug(去重),用于链接台账。

    generate_api_key

    fn generate_api_key() -> String raise EntropyUnavailable

    生成随机 API key:mk_ 前缀 + 平台熵源生成的 32 字节 hex。 熵源不可用时失败,绝不回退到可预测的伪随机种子。

    generate_session_secret

    fn generate_session_secret() -> String raise EntropyUnavailable

    生成仅用于当前进程的随机签名密钥。

    hash_api_key

    fn hash_api_key(key : String) -> String

    计算 API key 的 sha256 哈希(hex 编码)

    issue_jwt

    fn issue_jwt(secret : String, subject : String) -> String

    使用 HS256 生成 JWT。token 的有效期由进程内 session registry 控制。

    render_doc_html

    fn render_doc_html(raw : String, slug : String) -> String

    渲染一篇 Markdown 文档为可展示的 HTML 段落(不含页面骨架)。 slug 用于把正文里的相对链接解析成 /d/{slug}
    fn rewrite_wiki_links(html : String, base_slug : String) -> String

    将已渲染的 Markdown HTML 中的相对 Wiki 链接重写为 /d/{slug} 形式。 base_slug 是当前文档的 slug(用于解析相对路径目录)。 规则:不以 /http(s):#mailto: 开头的目标视为站点内相对链接, 基于 base_slug 的目录解析成绝对 /d/{slug},并去掉 .md 后缀。

    valid_slug

    fn valid_slug(slug : String) -> Bool

    slug 路径安全校验:只允许字母数字、-_/ 禁止 ../. 段逃逸和绝对路径。

    verify_jwt

    fn verify_jwt(secret : String, token : String) -> String?