<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>SQLite on Rovina</title><link>https://blog.rovina.top/tags/sqlite/</link><description>Recent content in SQLite on Rovina</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Fri, 30 Oct 2026 18:48:00 +0800</lastBuildDate><atom:link href="https://blog.rovina.top/tags/sqlite/index.xml" rel="self" type="application/rss+xml"/><item><title>Hearth / 炉火：自托管的家庭照片与文件库</title><link>https://blog.rovina.top/post/hearth%E7%82%89%E7%81%AB/</link><pubDate>Fri, 30 Oct 2026 18:48:00 +0800</pubDate><guid>https://blog.rovina.top/post/hearth%E7%82%89%E7%81%AB/</guid><description>&lt;h2 id="概述">概述
&lt;/h2>&lt;p>&lt;a class="link" href="https://github.com/rovinax/hearth" target="_blank" rel="noopener"
>Hearth&lt;/a>（炉火）是一个自托管的家庭资料库。仓库主页上的一句话是：&lt;/p>
&lt;blockquote>
&lt;p>Your family&amp;rsquo;s photos, videos, and files — warm, private, self-hosted.&lt;br>
家用照片、视频与文件归档：私密、自托管、开箱即用。&lt;/p>&lt;/blockquote>
&lt;p>它把上传、浏览、家庭分组和整理放在同一套 HTTP API 上。服务端是 Go，Web 界面是 React，构建后嵌进同一个二进制。本仓库以服务端和 Web 为主；若另有 Android 客户端，README 说明它可以对接同一套 HTTP API，而不是放在这个仓库里。&lt;/p>
&lt;p>下文依据 GitHub 上 &lt;code>main&lt;/code> 分支的 README、许可证和公开代码，不补充仓库里没有写明的部署规模或使用人数。&lt;/p>
&lt;h2 id="程序怎么拼在一起">程序怎么拼在一起
&lt;/h2>&lt;table>
&lt;thead>
&lt;tr>
&lt;th>部分&lt;/th>
&lt;th>仓库中的位置&lt;/th>
&lt;th>作用&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>主进程&lt;/td>
&lt;td>&lt;code>cmd/hearth&lt;/code>&lt;/td>
&lt;td>HTTP 服务，默认监听 &lt;code>:8080&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>MCP&lt;/td>
&lt;td>&lt;code>cmd/hearth-mcp&lt;/code>&lt;/td>
&lt;td>stdio 服务，供 Cursor 等客户端调用同一套 API&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Web&lt;/td>
&lt;td>&lt;code>web/&lt;/code>&lt;/td>
&lt;td>React 19 + Vite；&lt;code>web/ui.go&lt;/code> 用 &lt;code>embed&lt;/code> 装入 &lt;code>web/dist&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>存储&lt;/td>
&lt;td>&lt;code>internal/store&lt;/code>&lt;/td>
&lt;td>SQLite（&lt;code>modernc.org/sqlite&lt;/code>），迁移用 goose&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>媒体&lt;/td>
&lt;td>&lt;code>internal/media&lt;/code>&lt;/td>
&lt;td>按文件名和文件头分类，并包含 EXIF、缩略图相关代码&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;code>go.mod&lt;/code> 写明模块名 &lt;code>hearth&lt;/code>，Go 1.27。&lt;code>Makefile&lt;/code> 里的 &lt;code>build&lt;/code> / &lt;code>mcp&lt;/code> 都使用 &lt;code>CGO_ENABLED=0&lt;/code>。Docker 构建分两段：Node 24 打前端，Go 1.27 打二进制，最终镜像基于 Alpine 3.22，并安装 &lt;code>ffmpeg&lt;/code> 与 &lt;code>vips-tools&lt;/code>。&lt;code>docker-compose.yml&lt;/code> 把数据放在命名卷 &lt;code>hearth-data&lt;/code>，并用 &lt;code>GET /healthz&lt;/code> 做健康检查。&lt;/p>
&lt;p>GitHub Actions（&lt;code>.github/workflows/ci.yml&lt;/code>）在推送和 pull request 上执行：安装 Go 1.27 与 Node 24、构建前端、&lt;code>go test ./...&lt;/code>，再以 &lt;code>CGO_ENABLED=0&lt;/code> 编译主程序。&lt;/p>
&lt;h2 id="文件合集与权限">文件、合集与权限
&lt;/h2>&lt;p>文件的 &lt;code>media_kind&lt;/code> 只允许四类：&lt;code>image&lt;/code>、&lt;code>video&lt;/code>、&lt;code>audio&lt;/code>、&lt;code>other&lt;/code>。分类函数会看扩展名和文件头；只有音轨的 mp4 / m4a / webm 会归到音频，避免被当成视频打开。图片扩展名覆盖 jpeg、png、gif、webp、heic / heif。&lt;/p>
&lt;p>可见性字段只有两种：&lt;code>private&lt;/code> 与 &lt;code>shared&lt;/code>。README 把 &lt;code>shared&lt;/code> 解释为登录用户可见。除此之外，合集还可以授权给指定用户或用户组；管理员在检索等接口中可以越过普通成员限制。&lt;/p>
&lt;p>合集最初有单一的 &lt;code>media_kind&lt;/code>。后续迁移增加了 &lt;code>media_kinds&lt;/code>，因此一个合集可以同时容纳多种类型。合集、用户组成员、合集成员和收藏都在同一组表里：&lt;code>collections&lt;/code>、&lt;code>groups&lt;/code>、&lt;code>group_members&lt;/code>、&lt;code>collection_users&lt;/code>、&lt;code>collection_groups&lt;/code>、&lt;code>favorites&lt;/code>。&lt;/p>
&lt;p>误删走回收站：&lt;code>files.deleted_at&lt;/code> 非空表示已删除，检索默认不返回这些行。收藏是用户与文件的一对记录，按用户和时间索引。&lt;/p>
&lt;p>分享链接单独存放。&lt;code>shares&lt;/code> 表记录文件、创建者和 &lt;code>expires_at&lt;/code>。MCP 创建分享时，过期可选 &lt;code>never&lt;/code>（默认）、&lt;code>7d&lt;/code> 或 &lt;code>30d&lt;/code>，对应 &lt;code>POST /api/files/{id}/shares&lt;/code>。README 写明这是无需登录的预览链接。&lt;/p>
&lt;p>示例配置里，单次上传上限为 2147483648 字节（2 GiB），会话默认 168 小时。真实的 &lt;code>hearth.toml&lt;/code> 在 &lt;code>.gitignore&lt;/code> 中，仓库只提供 &lt;code>config.example.toml&lt;/code>。&lt;/p>
&lt;h2 id="主题">主题
&lt;/h2>&lt;p>界面主题写在 &lt;code>web/src/styles.css&lt;/code>。除默认配色外，还有五种具名主题：&lt;code>pine&lt;/code>、&lt;code>dusk&lt;/code>、&lt;code>sand&lt;/code>、&lt;code>sea&lt;/code>、&lt;code>sakura&lt;/code>。&lt;code>sakura&lt;/code> 使用浅粉背景和 &lt;code>#E891A8&lt;/code> 强调色。&lt;code>App.jsx&lt;/code> 按当前用户的 &lt;code>theme&lt;/code> 字段设置 &lt;code>document.documentElement.dataset.theme&lt;/code>。&lt;/p>
&lt;h2 id="检索">检索
&lt;/h2>&lt;p>检索走 SQLite FTS5，分词器是 &lt;code>unicode61 remove_diacritics 2&lt;/code>。&lt;/p>
&lt;ul>
&lt;li>&lt;code>files_fts&lt;/code> 索引文件名，以及该文件所属合集名&lt;/li>
&lt;li>&lt;code>collections_fts&lt;/code> 索引合集名&lt;/li>
&lt;li>插入、删除、改名时由触发器维护索引&lt;/li>
&lt;/ul>
&lt;p>&lt;code>GET /api/search?q=&lt;/code> 由 &lt;code>Store.Search&lt;/code> 实现。查询会先截断到 200 个 Unicode 字符，再按空白拆成词。每个词变成带前缀的 FTS 查询（&lt;code>&amp;quot;词&amp;quot;*&lt;/code>），词与词之间是 &lt;code>AND&lt;/code>。代码注释说明，这样英文词干和中文前缀都能配合 &lt;code>unicode61&lt;/code> 使用。&lt;/p>
&lt;p>未指定排序时，文件和合集都按同一优先级：&lt;/p>
&lt;ol>
&lt;li>名称精确匹配（不区分大小写）&lt;/li>
&lt;li>名称前缀&lt;/li>
&lt;li>名称中包含&lt;/li>
&lt;li>其余命中&lt;/li>
&lt;/ol>
&lt;p>同级再按 &lt;code>bm25()&lt;/code> 从低到高，然后才是创建时间或名称。已进回收站的文件不会出现。权限与列表一致：管理员、所有者、&lt;code>shared&lt;/code>，或该用户能访问的合集。默认最多返回 200 个文件和 50 个合集。&lt;/p>
&lt;p>测试 &lt;code>TestSearchFTSRankingAndPermissions&lt;/code> 覆盖了精确名先于前缀、前缀先于包含，以及私有文件不会被其他普通用户搜到等情况。&lt;/p>
&lt;h2 id="mcp">MCP
&lt;/h2>&lt;p>&lt;code>make mcp&lt;/code> 生成 &lt;code>bin/hearth-mcp&lt;/code>。进程名是 &lt;code>hearth&lt;/code>，实现版本字符串为 &lt;code>0.1.0&lt;/code>。它通过环境变量连接已经在跑的 Hearth：&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>变量&lt;/th>
&lt;th>是否必需&lt;/th>
&lt;th>默认&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>HEARTH_BASE_URL&lt;/code>&lt;/td>
&lt;td>否&lt;/td>
&lt;td>&lt;code>http://127.0.0.1:8080&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>HEARTH_USERNAME&lt;/code>&lt;/td>
&lt;td>是&lt;/td>
&lt;td>—&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>HEARTH_PASSWORD&lt;/code>&lt;/td>
&lt;td>是&lt;/td>
&lt;td>—&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>示例描述符在 &lt;code>mcp/hearth.mcp.json&lt;/code>，其中的用户名和密码是占位符。当前注册的工具有：&lt;/p>
&lt;ul>
&lt;li>&lt;code>hearth_whoami&lt;/code>&lt;/li>
&lt;li>&lt;code>hearth_list_files&lt;/code>&lt;/li>
&lt;li>&lt;code>hearth_search&lt;/code>&lt;/li>
&lt;li>&lt;code>hearth_list_collections&lt;/code>&lt;/li>
&lt;li>&lt;code>hearth_create_collection&lt;/code>&lt;/li>
&lt;li>&lt;code>hearth_list_groups&lt;/code>&lt;/li>
&lt;li>&lt;code>hearth_rename_group&lt;/code>&lt;/li>
&lt;li>&lt;code>hearth_file_metadata&lt;/code>&lt;/li>
&lt;li>&lt;code>hearth_set_favorite&lt;/code>&lt;/li>
&lt;li>&lt;code>hearth_create_share&lt;/code>&lt;/li>
&lt;/ul>
&lt;h2 id="在本机跑起来">在本机跑起来
&lt;/h2>&lt;p>README 要求 Go 1.27+；只有构建前端时才需要 Node.js 24。首次启动若还没有可用管理员，会用环境变量创建，之后再次启动不会改密码。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">cp config.example.toml hearth.toml
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">export&lt;/span> &lt;span class="nv">HEARTH_ADMIN_USER&lt;/span>&lt;span class="o">=&lt;/span>admin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">export&lt;/span> &lt;span class="nv">HEARTH_ADMIN_PASSWORD&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;change-me-now&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">make web
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">go &lt;span class="nb">test&lt;/span> ./...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">make run
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;code>make run&lt;/code> 监听 &lt;code>:8080&lt;/code>，浏览器打开 &lt;code>/login&lt;/code>。前端开发时另开一个终端，在 &lt;code>web/&lt;/code> 里执行 &lt;code>npm run dev&lt;/code>，把 &lt;code>/api&lt;/code> 代理到 &lt;code>127.0.0.1:8080&lt;/code>。也可以直接 &lt;code>docker compose up --build&lt;/code>。&lt;/p>
&lt;p>示例 systemd 单元在 &lt;code>deploy/hearth.service&lt;/code>：从 &lt;code>/etc/hearth/hearth.toml&lt;/code> 读配置，工作目录 &lt;code>/var/lib/hearth&lt;/code>。&lt;/p>
&lt;h2 id="许可证与贡献">许可证与贡献
&lt;/h2>&lt;p>仓库使用 MIT 许可证，版权声明为 Copyright (c) 2026 Junxiao and contributors。README 为中英对照。贡献说明要求改动保持聚焦，用 &lt;code>go test ./...&lt;/code> 验证，涉及检索时覆盖中英文文件名，并且不要提交 &lt;code>hearth.toml&lt;/code>、真实密码、本机绝对路径、上传日志或 &lt;code>data/&lt;/code>。&lt;/p>
&lt;p>代码与说明见 &lt;a class="link" href="https://github.com/rovinax/hearth" target="_blank" rel="noopener"
>github.com/rovinax/hearth&lt;/a>。&lt;/p></description></item></channel></rss>