轻量自托管书签管理。Chrome 插件管理书签,服务端集中存储与备份;可选安装桌面效率插件(Alfred workflow / Raycast 插件)与本机同步 CLI,让书签在本地秒级可查。
┌─────────────┐ REST API ┌──────────────────┐
│ Chrome 插件 │ ────────────▶ │ 服务端 (Rust) │
│ (增删查改) │ ◀──────────── │ sqlite + Web UI │
└─────────────┘ └────────▲─────────┘
│ 读写 │ POST /api/sync
▼ │ 双向增量同步
┌──────────────────────────────────────┴─┐
│ ~/.markmax/bookmarks.json(本地缓存) │ ← 开放格式,任何工具可读
└──────────────▲─────────────────────────┘
│ 监听文件变化 + 定时拉取(可选组件)
┌────────┴─────────┐
│ markmax-sync CLI │ ← brew services 常驻后台,仅桌面效率插件需要
└────────▲─────────┘
│ 直接读写缓存
┌────────┴─────────┐
│ Alfred mk / mka │
│ Raycast 搜索/收藏 │
└──────────────────┘
| 目录 | 端 | 说明 |
|---|---|---|
server/ |
服务端 | Rust (axum + sqlite) + React/Tailwind 管理界面(必装) |
extension/ |
Chrome 插件 (MV3) | 直连服务端 REST API,跨浏览器通用(必装) |
cli/ |
本机同步工具 | 维护本地缓存供桌面效率插件秒级读取;文件变更即时同步 + 定时同步(可选,仅使用 Alfred / Raycast 时需要) |
alfred/ |
Alfred workflow | mk 搜索浏览、mka 快速新增(可选) |
raycast/ |
Raycast 插件 | 与 Alfred 插件功能对等,直接读写本机缓存(可选) |
| Chrome 插件 | Web 管理端 | Alfred 插件 |
|---|---|---|
![]() |
![]() |
![]() |
数据模型(三端统一):书签为扁平记录,folder 为 / 分层的字符串路径(如 工作/项目A);时间戳统一 unix 毫秒;删除一律软删除(deleted + deleted_at),以便多端传播。
{
"id": "uuid",
"title": "Example",
"url": "https://example.com",
"tags": ["rust", "backend"],
"notes": "",
"folder": "work/dev",
"created_at": 1780000000000,
"updated_at": 1780000000000,
"deleted": false,
"deleted_at": null
}从零到可用分两步(必装),桌面效率工具(Alfred / Raycast 等)为可选附加。
# 1. 启动服务端(Docker Hub 镜像,amd64/arm64)
docker run -d --name markmax -p 8080:8080 -v markmax-data:/data \
--restart unless-stopped doom40k/markmax-server
# token 在日志里打印,同时持久化在容器 /data/token
# 2. 安装 Chrome 插件:从 Releases 页下载 markmax-extension-<版本>.zip 并解压
# chrome://extensions → 开发者模式 → 加载已解压的扩展程序 → 选解压出的 markmax-extension 目录
# 点插件图标 → 填服务端地址 + token → 连接日常使用:浏览器里用插件增删改书签;Web 管理界面(http://localhost:8080)提供完整管理能力(搜索、文件夹、标签、回收站、批量导入)。界面已适配手机端:手机浏览器直接打开即可用,也可「添加到主屏幕」以全屏 PWA 方式使用,无需安装 App。数据全部存在服务端,换机器无需迁移。
不装效率插件则本步完全跳过。CLI 的目的是在本地维护一份书签缓存,让效率插件搜索不经过网络、毫秒级出结果;装了插件就同时需要常驻的 CLI 保持缓存新鲜。
# 1. 安装 CLI(CI 预编译二进制,秒装,无需 Rust 环境)
brew tap doom40k/tools https://github.com/doom40k/homebrew-tools
brew install doom40k/tools/markmax
# 2. 配置并常驻后台(首次配置需交互式)
markmax-sync --config # 缓存目录(默认 ~/.markmax) + 服务端地址 + API token
brew services start markmax # LaunchAgent 常驻,崩溃自动拉起
# 3a. 安装 Alfred workflow:双击 alfred/dist/markmax.alfredworkflow
# 3b. 或安装 Raycast 插件:见 raycast/README.md(构建后导入 dist 目录)本地缓存
~/.markmax/bookmarks.json是开放格式(见「cli — 缓存目录格式」),你可以自己写 Keyboard Maestro / 命令行脚本等工具直接读取它,不依赖 Alfred 与 markmax-sync。仓库已内置 Raycast 插件(raycast/,与 Alfred 功能对等),安装见raycast/README.md。
Rust 实现,SQLite(WAL)存储,提供 REST API + 静态托管的 Web 管理界面(React + Tailwind,Vercel 风格黑白配色)。Web 界面支持搜索、新建编辑、文件夹树管理(新建/重命名/删除)、标签过滤、回收站恢复、批量导入书签 HTML;响应式布局,窄屏下侧边栏变为抽屉、操作直接可点,并支持 PWA 添加到主屏幕(iOS / Android,图标全屏、无地址栏)。
命令行参数与环境变量等价(环境变量优先级低于参数):
| 参数 | 环境变量 | 默认值 | 说明 |
|---|---|---|---|
--port |
MARKMAX_PORT |
8080 |
监听端口 |
--data-dir |
MARKMAX_DATA_DIR |
./data |
sqlite 数据库与 token 存放目录 |
--token |
MARKMAX_TOKEN |
自动生成 | API token,省略时自动生成并持久化到 <data-dir>/token |
--web-dir |
MARKMAX_WEB_DIR |
web/dist |
管理界面静态目录 |
所有 /api/* 接口(除 /api/health)都需要请求头 Authorization: Bearer <token>。
官方镜像发布在 Docker Hub,支持 linux/amd64 与 linux/arm64(两个平台分别在 GitHub 原生 runner 上构建):
docker run -d --name markmax \
-p 8080:8080 \
-v markmax-data:/data \
--restart unless-stopped \
doom40k/markmax-server也可以本地从源码构建:cd server && docker build -t markmax-server .(把上面命令中的镜像名换成 markmax-server)。
容器内可用环境变量:
| 环境变量 | 说明 | 默认值 |
|---|---|---|
MARKMAX_PORT |
容器内监听端口(改端口需同时映射 -p 宿主:容器) |
8080 |
MARKMAX_DATA_DIR |
数据存储目录(sqlite + token 文件),务必挂载 volume 持久化 | /data |
MARKMAX_TOKEN |
固定 API token;不设则首次启动自动生成并持久化到 <数据目录>/token,日志中也会打印 |
自动生成 |
自定义示例:
docker run -d -p 9000:9000 \
-e MARKMAX_PORT=9000 \
-e MARKMAX_TOKEN=my-secret-token \
-v markmax-data:/data \
doom40k/markmax-server注意:不要用
docker run … markmax-server --port xxx覆盖端口——CLI 参数优先级高于环境变量,会令MARKMAX_PORT失效。镜像已内置默认值,直接用环境变量即可。
新版本发布后(主仓库打新 tag,CI 自动构建并推送 doom40k/markmax-server:<版本> 与 latest),按以下流程更新容器。数据全部在 Docker volume(markmax-data)里,重建容器不会丢失任何书签;不放心可先备份:docker run --rm -v markmax-data:/data -v $(pwd):/backup alpine tar czf /backup/markmax-backup.tar.gz -C /data .
# 1. 查看当前容器状态与镜像版本
docker ps --filter name=markmax # 容器在运行即正常
docker inspect markmax --format '{{.Image}}' # 当前镜像
# 2. 拉取新镜像(默认 latest;要固定版本用 :0.1.9 等 tag)
docker pull doom40k/markmax-server
# 3. 停止并删除旧容器(volume 数据保留)
docker rm -f markmax
# 4. 用相同的参数重新创建容器(如改过端口/token,保持与原来一致)
docker run -d --name markmax -p 8080:8080 -v markmax-data:/data \
--restart unless-stopped doom40k/markmax-server
# 5. 验证
docker ps --filter name=markmax
curl -s http://localhost:8080/api/health # 期望 {"status":"ok",...}镜像 tag 说明:
latest总指向最新版,跟随自动更新;生产环境建议固定具体版本 tag(如:0.1.9),确认无问题后再手动升级,避免latest意外变更。
# 先构建管理界面
cd server/web && npm install && npm run build && cd ..
cargo build --release
MARKMAX_DATA_DIR=/var/lib/markmax ./target/release/markmax-server生产建议配合 systemd 常驻:
# /etc/systemd/system/markmax.service
[Unit]
Description=markmax bookmark server
After=network.target
[Service]
ExecStart=/opt/markmax/markmax-server
Environment=MARKMAX_DATA_DIR=/var/lib/markmax
Environment=MARKMAX_PORT=8080
Restart=always
User=markmax
[Install]
WantedBy=multi-user.targetsystemctl enable --now markmax 启动;日志走 journalctl(journalctl -u markmax -f)。
服务端自带 Bearer token 鉴权,可置于 nginx/caddy 之后对外。建议:
- 仅 HTTPS 暴露(token 明文传输会被截获)
- 不需要改动路径前缀:API 全部在
/api/*,界面在根路径
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
}| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/health |
健康检查(无需 token) |
| GET | /api/bookmarks |
列表;query 参数:q(模糊搜索 title/url/notes/tags)、folder(前缀匹配,含子文件夹)、tag(精确匹配)、deleted(0 正常 / 1 回收站)、limit(默认 100,最大 5000)、offset |
| POST | /api/bookmarks |
新建;body:{ title, url*, tags[], notes, folder },url 必填,folder 自动登记为文件夹 |
| POST | /api/bookmarks/import |
批量导入;body:{ bookmarks: [{ title, url, tags[], notes, folder, created_at? }] },单次最多 10000 条,无效记录(url 为空)跳过,单事务插入并自动登记文件夹 |
| PATCH | /api/bookmarks/{id} |
局部更新,字段可省略 |
| DELETE | /api/bookmarks/{id} |
软删除(移入回收站) |
| POST | /api/bookmarks/{id}/restore |
从回收站恢复 |
| GET | /api/folders |
文件夹列表(含空文件夹),返回 { folders: [{ name, count }] },count 为精确匹配书签数 |
| POST | /api/folders |
新建文件夹;body:{ name },可用 / 分层 |
| PATCH | /api/folders |
重命名;body:{ name, new_name },子文件夹与书签路径前缀整体跟随 |
| DELETE | /api/folders |
删除;body:{ name },其下书签变为未分类(folder 置空) |
| POST | /api/sync |
同步(见下) |
CLI 与服务端之间的同步,单次请求完成双向:
// 请求:since 为客户端上次同步时间戳(0 表示全量),changes 为客户端本地自上次同步以来的改动
{ "since": 1780000000000, "changes": [ { ...bookmark } ] }
// 响应:changes 为服务端 updated_at >= since 的全部记录(含软删除,按时间升序)
{ "server_time": 1780000001234, "changes": [ { ...bookmark } ] }- 冲突解决:按
updated_at最后写入者胜(last-write-wins),id相同则比较时间戳。 - 服务端先应用客户端 changes(
created_at保留客户端值),再返回客户端缺失的记录。 - 客户端收到响应后同样按
updated_at合并;本端时间戳更新则保留本端。删除通过软删除标记传播。 - 客户端应在合并完成后将本地
last_sync推进为响应中最大的时间戳(无记录时用server_time)。
管理界面工具栏的「导入」按钮支持 Chrome、Raindrop 导出的书签 HTML(Netscape 格式):
- 解析在浏览器端完成(原生
DOMParser,无需额外依赖),文件夹层级按/拼接保留,TAGS/keywords属性解析为标签,ADD_DATE保留为创建时间。 - 解析结果先预览(书签数、文件夹数、样例)再确认导入,导入走
POST /api/bookmarks/import批量落库。
非必装组件:只有使用 Alfred 等桌面效率工具时才需要。它的职责是把服务端的书签同步一份到本地缓存 ~/.markmax/bookmarks.json,并作为后台服务(brew services 常驻)监听文件变化即时同步、每 3 分钟定时拉取服务端变更(双向同步按 updated_at 最后写入者胜)。
效率工具的搜索直接读本地缓存、不走网络,因此毫秒级出结果;缓存也是开放格式,其它工具可自行读取(见下)。
brew tap doom40k/tools https://github.com/doom40k/homebrew-tools
brew install doom40k/tools/markmax安装的是 CI 预编译二进制(macOS Apple Silicon / Intel 双架构),无需本地 Rust 环境,秒装。首次使用先在终端完成配置(交互式):markmax-sync --config;无交互终端(如后台服务)下运行会直接报错退出,不会卡在配置流程。
brew services start markmax # 常驻后台(用户态 LaunchAgent,keep_alive 崩溃自动拉起)
brew services stop markmax # 停止日志输出到 $(brew --prefix)/var/log/markmax.log(Homebrew 规范位置)。
brew update && brew upgrade markmax # 拉取新 formula 并升级二进制
brew services restart markmax # 重启后台服务使新版本生效
markmax-sync --version # 确认版本发布链路:主仓库改版本号 → 打新 tag → CI 自动构建双架构二进制附到 release → 更新 tap 中 formula 的 version 与 sha256 → 用户
brew upgrade生效。
cd cli && cargo install --path . # 或 cargo build --release首次启动无配置时自动进入交互式配置流程:缓存目录路径、服务端地址、API token(密码式输入)。
| 命令 / 参数 | 说明 |
|---|---|
markmax-sync |
启动 daemon:监听缓存变化即时同步 + 定时拉取 |
--cache-dir <路径> |
指定缓存目录(跳过交互) |
--server / --token |
覆盖服务端地址 / token |
--sync |
立即同步一次后退出 |
--config |
重新交互式配置 |
--interval <秒> |
定时同步间隔,默认 180(3 分钟) |
search <词> [--alfred] [--limit] |
搜索本地缓存;TSV 输出,--alfred 输出 Script Filter JSON |
add --url <u> [--title] [--folder] [--tags] [--notify] |
快速新增一条书签到缓存,daemon 自动同步 |
remove <id> [--notify] |
移入回收站(软删除) |
install-alfred |
把 Alfred workflow 安装到 Alfred 配置目录(注入二进制绝对路径) |
~/.markmax/
├── markmax-config.json # 全局配置:{ cache_dir, server, token, last_sync }
├── bookmarks.json # 书签数据(原子写入)
└── folders.json # 已有文件夹列表(Alfred mka 的选择项来源)
bookmarks.json 单条记录结构见文首数据模型。这是稳定的开放格式:如果你要自己写 Raycast / Keyboard Maestro / 终端脚本等工具,直接读取该文件即可,无需依赖 Alfred workflow 或 markmax-sync 的其它能力;用 markmax-sync search <词> 命令也能拿到同样的结构化输出。约定:
- 其它进程增删改:直接改写
bookmarks.json(保留完整字段即可),CLI 监听文件变化后自动同步。 - 删除用软删除(
deleted: true+deleted_at),同步确认后 CLI 会自动清理 tombstone。 - 同步细节:本地
updated_at > last_sync的记录作为变更推送;响应中时间戳更新的记录覆盖本地;last_sync推进为服务端时间(各端时钟应大致一致)。
MV3 插件,Vercel 黑白风格,直连服务端 REST API(跨浏览器通用:Chrome / Edge / Brave 等 Chromium 系均可加载)。
- 从 Releases 下载最新的
markmax-extension-<版本>.zip(附带.sha256校验文件)并解压 - 打开
chrome://extensions(Brave 为brave://extensions)→ 开发者模式 → 「加载已解压的扩展程序」→ 选择解压出的markmax-extension目录 - 点插件图标 → 填写服务端地址(如 http://localhost:8080)与 API token(服务端启动日志或
server/data/token)→ 连接
插件无构建步骤(纯静态文件),CI 打包时会把 manifest 版本号同步为 release tag 版本,解压即用,无需 clone 仓库。从源码安装:clone 后直接加载
extension/目录(开发者日常开发用这种方式)。
- 增删查改:列表搜索(
/聚焦)、新建、编辑、软删除(进回收站)、复制链接、新标签页打开 - 配置引导:首次使用弹配置表单;token 无效 / 无法连接服务端分别给出明确提示
- 设置页(选项):服务端地址 + token 配置、测试连接、书签概况、清除配置
- 权限最小化:仅
storage(存配置)+host_permissions(访问服务端 API),无其它权限
注:插件直连服务端,不经本地缓存。早期 Native Messaging(读写本地文件)方案因 Brave 清单读取行为不一致而弃用。
依赖已安装的 markmax-sync CLI(推荐 brew 安装,或 cargo install --path cli 从源码构建)。
- 分发包:双击
alfred/dist/markmax.alfredworkflow导入,脚本自动定位 CLI($MARKMAX_CLI→PATH→~/.cargo/bin) - 开发者:
markmax-sync install-alfred(把模板复制进 Alfred 配置目录并注入二进制绝对路径)
| 关键词 | 功能 |
|---|---|
mk |
搜索浏览书签。空查询列出顶层文件夹 + 未分类;⇥ 进入文件夹(支持多级与继续输入过滤);输入关键词全局搜索 title/url/notes/tags;↩ 打开链接,⌘↩ 复制链接 |
mka |
快速收藏当前页面。需在 Chrome / Edge / Brave / Safari 前台触发,抓取活动标签页;下拉列出已有文件夹(来自 folders.json)供选择,回车存入,系统通知确认(带项目图标) |
首次使用 macOS 会请求「自动化」授权,请允许。mk 的防抖节奏可在 Alfred Preferences → 该 workflow 的 Script Filter Run Behaviour 里调整。
与 Alfred 插件功能对等,两者可并存。与 Alfred 的区别:代码上不依赖 CLI 二进制——搜索直接读本机缓存,收藏直接原子写入缓存(不经任何外部命令)。但仍需 CLI daemon 常驻(brew services start markmax):daemon 是缓存与服务端之间的搬运工,没有它缓存就是死数据——搜索结果不会更新,收藏的书签也不会同步到服务端。
cd raycast
npm install
npm run build # 产出编译后的扩展到 dist/ 目录Raycast 偏好设置(⌘,)→ Extensions → + → Import Extension → 选择 raycast/dist 目录(编译产物目录,不是项目根目录),永久安装。代码更新后重新 build 并重新导入。
| 命令 | 功能 |
|---|---|
| 搜索书签(search) | 空查询列出顶层文件夹 + 未分类(含条目数);↩ 进入文件夹逐级浏览,框内输入继续过滤;有关键词时全局搜索 title/url/notes/tags/folder,文件夹名命中可直接进入;↩ 打开链接,⌘C 复制链接,⇧⌘C 复制标题 |
| 收藏当前页面(add-bookmark) | 抓取浏览器当前活动标签页;首项「存为未分类」,其余为已有文件夹列表(来自 folders.json),回车存入并弹 toast 确认 |
首次使用收藏命令时 Raycast 会提示安装官方浏览器扩展(Chrome / Edge / Arc / Firefox 等均有)并授权标签页访问,按引导完成即可。缓存目录默认 ~/.markmax,可在扩展偏好中修改(需与 CLI daemon 一致)。
bookmark/
├── server/ # 服务端(Rust axum + sqlite + web/ 管理界面 + Dockerfile)
├── extension/ # Chrome 插件(MV3)
├── cli/ # 本机同步工具(Rust CLI)
├── alfred/ # Alfred workflow(template/ 模板,build.sh 打包出 dist/*.alfredworkflow)
├── raycast/ # Raycast 插件(与 Alfred 功能对等,直接读写本机缓存)


