内容
<div align="center">
# **Quire**博客 `v1.2.6`
**一个由AI操作的个人博客平台。**
从干净的多语言管理后台撰写和发布 - 或者将密钥交给AI代理,让它为您撰写、发布,甚至部署。
<br/>









[**🌐 Live demo**](https://manhhung.me) · [**获取您自己的副本**](#-获取您自己的副本) · [**让AI代理运行它**](#-让AI代理撰写发布mcp) · [**架构**](./ARCHITECTURE.md) · [**路线图**](./ROADMAP.md) · [**许可证**](#-许可证)
<sub>**manhhung.me**上的演示是作者的个人博客 - 一个现场实例,展示该*平台*的运行效果,而不是内容展示(忽略内容,看它是如何工作的)。</sub>
<br/>
<img src="docs/demo.jpg" alt="Quire 博客管理后台和阅读视图" width="900">
</div>
---
## ✨ 它是什么
一个**开源**(MIT)的单用户博客,适用于那些只想**写作**的人。公共站点静态缓存,因此在**移动设备和桌面设备上加载极快**,并且针对**易读的排版**进行了优化 - 清晰的阅读体验至上。所有内容都**易于在后台自定义**(调色板、字体、菜单、字体),**没有任何硬编码值**,因此您可以自定义它,而无需接触代码。
所有写作都在精致的`/admin`(或通过MCP)中进行。文本存储在**Postgres**(Supabase on Vercel 或自托管时的捆绑Postgres);二进制文件(图像、文件、图标)通过可插拔的存储驱动程序 - 默认**Vercel Blob** 或 Docker上的**本地文件系统**。无需git推送发布,无需CMS管理。
| 区域 | 您获得的内容 |
|:---|:---|
| 🖋️ **编辑器** | TipTap 3 + Markdown · 响应式 `sharp` 图像(原始 + AVIF/WebP 变体) · 3 版本时间轴 · 离线本地自动保存 |
| 🎨 **外观** | 6 个可自定义的浅色 + 深色调色板 · 一个可调的字体系统(每个角色的尺寸/行距/跟踪,无硬编码尺寸) · 上传每个字体的自定义字体 |
| 🌍 **国际化** | 后台 + 站点支持 `en · vi · de · ja · zh · ko` |
| 🔍 **阅读** | 即时本地 + Postgres全文搜索 · 目录 · 相关帖子 · 阅读时间 · 进度条 |
| 📈 **内置** | 无cookie分析(浏览量/访客/热门页面,无PII) · 活动日志 · 软删除回收站(无自动清理) |
| 🔎 **SEO** | 站点地图 · RSS · `robots.txt` · `llms.txt` · 动态OG图像 - 均可切换 |
| 💾 **备份** | 一键式完整快照(DB + 所有二进制文件)到 **Google Drive**,可调度 + 恢复 |
| 🤖 **MCP** | 一个远程端点,允许AI代理使用与后台相同的规则撰写和发布博客 |
| 📱 **PWA** | 可安装,独立启动 |
| 🔐 **身份验证** | NextAuth v5 · Google登录 · 单个授权所有者 · 边缘保护后台/API |
| 🚀 **部署** | Vercel + Supabase (+ Vercel Blob),或使用Docker自托管 - 捆绑Postgres + 本地存储,**无需云** - 相同的代码库 |
> 构建于 **Next.js 16** (应用路由器,React 19,严格的TS)+ **Tailwind v4**,部署在 **Vercel** 或使用 **Docker**自托管。
**它适用于谁** - 想要一个快速、好看、完全属于自己的博客的人,在Vercel + Supabase上运行 **或使用Docker自托管**,并且喜欢让AI代理帮助运行博客的想法。
**不适用于** - 需要角色和编辑工作流的多作者团队/出版物。Quire Blog设计为单用户(一个授权电子邮件);多租户功能在计划中的SaaS中,不在此处。
---
## 🚀 获取您自己的副本
三种方式可以建立您自己的博客 - **请选择一种**。每种方式都可以拥有一个位于您域名的实时网站。
<details>
<summary><b>1️⃣ 自己动手</b> - 仪表盘中约10分钟</summary>
<br/>
1. **Fork** 此仓库(这样Vercel会部署**您的**副本)。
2. **数据库** - 创建一个 [Supabase](https://supabase.com) 项目 → SQL 编辑器 → 粘贴 [`scripts/schema.sql`](./scripts/schema.sql) → **运行**(幂等)。复制 **项目网址** + **`service_role`** 密钥(设置 → API)。
3. **导入到Vercel** - *添加新项目 → 项目* → 导入您的fork。
4. **Blob存储** - *存储 → 创建 → Blob*,将其连接到项目。这会自动注入 `BLOB_READ_WRITE_TOKEN`。
5. **环境变量** (设置 → 环境变量 - 参见 [表格](#-环境变量)):
`SUPABASE_URL`, `SUPABASE_SERVICE_ROLE_KEY`, `AUTH_SECRET` (`npx auth secret`), `AUTHORIZED_EMAIL`, `AUTH_GOOGLE_ID`, `AUTH_GOOGLE_SECRET`.
6. **部署**。然后在您的 [Google OAuth客户端](https://console.cloud.google.com/apis/credentials) 中添加重定向URI `https://<您的域名>/api/auth/callback/google`。
7. 打开 `https://<您的域名>/admin`,以 `AUTHORIZED_EMAIL` 身份登录,设置您的标题/调色板/菜单到 **设置**,然后开始写作。
</details>
<details>
<summary><b>2️⃣ 交给AI代理</b> - Claude, OpenAI Codex, OpenClaw, Hermes...</summary>
<br/>
给代理 **Vercel + Supabase + GitHub 访问权限** (令牌/CLI/MCP),然后粘贴:
```text
部署我自己的 Quire 副本:https://github.com/joiha-steven/Quire:
1. Fork 仓库到我的账户。
2. 创建一个 Supabase 项目,并在其 SQL 编辑器中运行 scripts/schema.sql。
3. 从 fork 创建一个 Vercel 项目;添加一个 Vercel Blob 存储(这设置了 BLOB_READ_WRITE_TOKEN)。
4. 带我逐步完成创建 Google OAuth “Web” 客户端的过程
(您无法登录我的 Google 帐户,因此请指导我完成 Cloud Console:
创建项目,配置同意屏幕,创建 OAuth 客户端,
并告诉我需要点击的确切内容),然后收集生成的
AUTH_GOOGLE_ID 和 AUTH_GOOGLE_SECRET。
5. 设置环境变量:SUPABASE_URL,SUPABASE_SERVICE_ROLE_KEY (Supabase → 设置 → API),
AUTH_SECRET (使用 `npx auth secret` 生成),AUTHORIZED_EMAIL=<我的电子邮件>,
AUTH_GOOGLE_ID 和 AUTH_GOOGLE_SECRET。
6. 部署,然后告诉我注册 https://<域名>/api/auth/callback/google
作为 Google 客户端的重定向 URI。
7. 返回实时网址。
```
> 代理执行其他操作;对于 Google OAuth 应用程序,它 **引导您完成点击**(它无法登录您的 Google 帐户)并从您那里获取客户端 ID/密钥。
</details>
<details>
<summary><b>3️⃣ 使用Docker自托管</b> - 您自己的服务器,无需云服务</summary>
<br/>
**完全自包含 - 无需云服务帐户**。堆栈捆绑 **Postgres + PostgREST** (替换Supabase)和 **本地文件系统**存储(替换Vercel Blob),以及一个cron sidecar。所有内容都在您的主机上运行;只有Google登录需要访问互联网。数据存储在 `./data/postgres` (文本)+ `./data/uploads` (二进制文件)中 - 备份这两个文件夹。
```bash
git clone https://github.com/joiha-steven/Quire.git && cd Quire
cp .env.docker.example .env.docker
node scripts/docker/gen-keys.mjs >> .env.docker # 数据库密码 + JWT 密钥 + 服务密钥
# 然后填写 AUTH_SECRET, AUTH_GOOGLE_ID/SECRET, AUTHORIZED_EMAIL, SITE_URL, CRON_SECRET
docker compose up -d --build # 应用程序在 :3000 + 数据库 + 休息 + cron
```
然后将反向代理/TLS指向端口 `3000`,并在Google OAuth客户端上注册 `<SITE_URL>/api/auth/callback/google`(和 `<SITE_URL>/api/backup/callback` 用于Drive备份)。映像 **无需后端环境即可构建** - 密钥在运行时提供。捆绑的DB在首次启动时自动应用 `scripts/schema.sql`。您更喜欢托管数据库吗?将 `SUPABASE_URL` 指向任意Supabase项目并删除 `db`/`rest` 服务 - 参见 [`.env.docker.example`](./.env.docker.example)。Vercel路径保持不变(Supabase + Blob)。
</details>
> [!TIP]
> **Vercel:** 两个 `vercel.json` 旋钮可以使您的 - `regions` (默认为 `sin1`/新加坡 - 设置您最近的)和 `maxDuration: 60` 用于上传(免费Hobby计划限制函数时间,因此修剪它或升级到Pro以支持大图片)。 **Docker:** 大型上传没有4.5MB的限制(浏览器直接发布到服务器),所以大图片直接生效。
---
## 🤖 让AI代理撰写和发布(MCP)
Quire Blog配备了一个远程 **MCP** 服务器,因此第二个AI代理可以运行您的博客 - 起草,编辑,标记,**直接发布到实时网站**。无需git,无需部署:内容通过与后台使用的相同数据层(以及相同的slug/revision/软删除规则)进入Supabase + Blob。
1. **启用它** - *后台 → 设置 → 高级 → MCP*,生成一个命名令牌(**仅显示一次**,存储时哈希,180天后过期)。
2. **连接您的代理** 到端点 `https://<您的域名>/api/mcp`,使用 `Authorization: Bearer <令牌>` (也支持OAuth连接器)。
3. **提示它**,例如:
```text
使用 Quire Blog MCP 服务器,撰写一篇标题为
“What I learned shipping a blog with an AI agent”的600字帖子,
赋予它标签
“ai”和“writing”,设置一个友好的摘要,并发布它。
```
帖子几秒钟内即可上线。MCP阻止敏感设置,您仍然是唯一的管理者 - 在后台撤销任何令牌,它就会消失。
---
## 🔑 环境变量
参见 [`.env.example`](./.env.example)。基本内容:
| 变量 | 必需 | 它是什么 · 在哪里获取 |
|---|:---:|---|
| `AUTH_SECRET` | ✅ | NextAuth 密钥 - 使用 `npx auth secret` 生成 |
| `AUTHORIZED_EMAIL` | ✅ | 唯一允许进入 `/admin` 的电子邮件 - 您的电子邮件 |
| `AUTH_GOOGLE_ID` / `AUTH_GOOGLE_SECRET` | ✅ | Google OAuth “Web” 客户端(后台登录 + 可选的评论者登录)- [Cloud Console → Credentials](https://console.cloud.google.com/apis/credentials) |
| `SUPABASE_URL` | ✅ | Supabase 项目 API URL - Supabase → 设置 → API。 **Docker:** 自动设置为捆绑的 PostgREST (`http://rest:3000`) |
| `SUPABASE_SERVICE_ROLE_KEY` | ✅ | Supabase `service_role` 密钥(密钥,服务器专用)- 相同的页面。 **Docker:** 使用 `node scripts/docker/gen-keys.mjs` 生成 |
| `POSTGRES_PASSWORD` / `SUPABASE_JWT_SECRET` | ◻️ Docker | 捆绑数据库的密钥,用于本地Postgres + PostgREST - 由 `gen-keys.mjs` 生成 |
| `BLOB_READ_WRITE_TOKEN` | ✅ Vercel | Vercel Blob 令牌 - **自动注入**,当您连接Blob存储时;还派生公共Blob URL。 **Docker上不使用** (本地文件系统驱动程序) |
| `STORAGE_DRIVER` | ◻️ Docker | `vercel-blob`(默认)或 `local`。Docker镜像内置 `local`;二进制文件进入 `STORAGE_LOCAL_DIR`(默认为 `/app/uploads`) |
| `SITE_URL` | ◻️ Docker | 实例的规范公共URL(Vercel会自动推断)。用于OG/站点地图/身份验证回调,自托管时 |
| `CRON_SECRET` | ◻️ 可选 | 保护 `/api/cron`(保持活动 + 调度备份)- 任意字符串。Docker上的cron sidecar会发送它 |
| `MCP_OAUTH_SECRET` | ◻️ 可选 | 签名MCP OAuth代码 - 随机;回退到 `AUTH_SECRET` |
| Turnstile / Facebook 密钥 | ◻️ 可选 | 评论反垃圾邮件(Cloudflare Turnstile)+ Facebook评论者登录 - **在后台设置**(服务器端存储)。匹配的环境变量(`TURNSTILE_*`,`AUTH_FACEBOOK_*`)仍然作为后备 |
MCP令牌和Google Drive备份连接在 **后台创建**,而非通过环境变量。密钥存储在 `.env.local`(gitignored)+ Vercel(`vercel env pull`)- 或自托管时的 `.env.docker`。您的博客内容存储在Supabase + Blob(或Docker上的捆绑Postgres + 本地卷),永远不会进入git。完整的自托管设置在 [`.env.docker.example`](./.env.docker.example)。
---
## 🧑💻 本地运行(可选)
```bash
git clone https://github.com/joiha-steven/Quire.git && cd Quire
npm install
cp .env.example .env.local # 填写上面的值
npx auth secret # `AUTH_SECRET`
npm run dev # http://localhost:3000/admin`
```
将相同的Supabase + Blob指向本地,并将 `http://localhost:3000/api/auth/callback/google` 添加到您的Google客户端。 `npm run check:all` 必须在进行任何更改之前通过(类型检查+ lint + 不变性检查+ vitest缝合测试;离线,无凭证);`npm run build` 用于发布。
---
## ⚙️ 使用它
- `/` - 公共博客(已发布,按日期到达的帖子)· `/category/<slug>`,`/tag/<slug>`(slugified)· 头部搜索叠加层 · 基于路径的分页(`/page/2`)。
- `/admin` - 仪表盘,编辑器,媒体,分析,设置(仅限所有者)。
- **设置** 是一个表单 / 一个保存,三个选项卡 - **常规**(站点,菜单,读者功能,SEO),**外观**(调色板,自定义字体,每个角色的文本大小表),**高级**(MCP,备份,自定义CSS)。所有内容都注入为CSS变量,因此更改会立即应用 **无需重新部署**。
- **性能:** 公共页面是ISR缓存的;每次后台保存都会清除受影响的页面,因此编辑后立即生效,无需提供过时内容。完整的设计 + 原因在 [`ARCHITECTURE.md`](./ARCHITECTURE.md)。
---
## 🗺️ 路线图
Docker自托管已发布(本地文件系统存储);接下来是一个S3/MinIO存储驱动程序 + 发布的GHCR镜像,从Markdown笔记应用(Obsidian → Craft)发布,以及编辑器中的可选AI辅助。参见 [`ROADMAP.md`](./ROADMAP.md)。
---
## 📄 许可证
两个独立的层级 - 保持它们不同:
- **代码(此仓库)- [MIT](./LICENSE).** 免费开源:使用,修改,重新分发或以任何目的出售,无需 **义务提及**(MIT仅要求许可证文本随副本)。 Fork它并运行您自己的博客。
- **内容 - 版权所有.** 使用Quire Blog发布的**内容**(文章,图像在操作员的站点上,例如manhhung.me)属于其作者,不受MIT保护,不允许在未经许可的情况下重用。