基于本服务器的部署经历,踩过的坑记录下来,由chatgpt润色

使用 Obsidian + LiveSync + Quartz 搭建自托管博客

这套方案将 Obsidian 作为内容编辑器,通过 Self-hosted LiveSync 同步到 CouchDB,再由 livesync-cli 将数据库内容还原成服务器上的 Vault,最后使用 Quartz 生成静态网站。

最终链路:

Obsidian PC / Android

        │ Self-hosted LiveSync

      CouchDB

        │ livesync-cli

/opt/obsidian/vault

        └── Public

              │ Quartz --watch

        Static HTML


      1Panel / OpenResty


         linvk.com

博客内容仍然是普通 Markdown 文件,不依赖 WordPress、数据库型 CMS 或 GitHub Pages。


一、目录结构

服务器统一使用:

/opt/obsidian/
├── livesync-data/
├── vault/
│   ├── Private/
│   └── Public/
│       ├── index.md
│       ├── Linux/
│       ├── Docker/
│       └── attachments/

└── quartz/
    └── quartz.config.yaml

1Panel 网站目录:

/opt/1panel/www/sites/linvk.com/index

其中:

Private/

用于私人笔记。

Public/

作为 Quartz 唯一的博客内容目录。

只要文件不进入 Public,就不会被博客发布。


二、部署 CouchDB

Obsidian Self-hosted LiveSync 使用 CouchDB 作为同步数据库。

可以通过 1Panel 应用商店或 Docker Compose 部署 CouchDB。

示例:

services:
  couchdb:
    image: couchdb:3.5.2
    container_name: obsidian-couchdb
    restart: unless-stopped
 
    environment:
      COUCHDB_USER: obsidian
      COUCHDB_PASSWORD: CHANGE_ME
 
    volumes:
      - ./data:/opt/couchdb/data
 
    ports:
      - "127.0.0.1:5984:5984"

建议不要直接将:

5984

暴露到公网。

通过 OpenResty 反向代理:

https://obsidian.example.com

访问 CouchDB。

例如:

location / {
    proxy_pass http://127.0.0.1:5984;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

然后在 CouchDB 中创建数据库,例如:

obsidiannotes

三、配置 Obsidian Self-hosted LiveSync

在 Obsidian 安装:

Self-hosted LiveSync

配置 CouchDB:

URI:
https://obsidian.example.com
 
Database:
obsidiannotes
 
Username:
obsidian
 
Password:
********

完成配置后,让 PC、Android 等设备均连接到同一个 CouchDB。

此时同步链路为:

Obsidian

CouchDB

但 CouchDB 中的数据还不能直接交给 Quartz。

还需要 livesync-cli 将数据同步为真实的 Markdown 文件。


四、部署 livesync-cli

创建目录:

mkdir -p /opt/obsidian/livesync-data
mkdir -p /opt/obsidian/vault

使用官方镜像:

ghcr.io/vrtmrz/livesync-cli:latest

Compose:

services:
  livesync-cli:
    image: ghcr.io/vrtmrz/livesync-cli:latest
    container_name: obsidian-livesync-cli
    restart: unless-stopped
 
    volumes:
      - /opt/obsidian/livesync-data:/data
      - /opt/obsidian/vault:/vault
 
    command:
      - --vault
      - /vault
      - --interval
      - "30"
      - daemon

首次使用时需要完成 livesync-cli 的远程配置。

之后 daemon 会定期从 CouchDB 同步。

正常情况下服务器会出现:

/opt/obsidian/vault/
├── Private/
├── Public/
├── xxx.md
└── attachments/

--interval 30 表示大约每 30 秒检查一次变化。


五、规划 Public 和 Private

建议 Vault 使用:

Vault/
├── Private/
│   ├── 私人笔记.md
│   ├── 服务器密码.md
│   └── 内部资料.md

└── Public/
    ├── index.md
    ├── hello-world.md
    ├── Linux/
    ├── Docker/
    └── attachments/

Quartz只读取:

/vault/Public

因此:

Private

不会进入构建过程。

这是整个博客最重要的安全边界。


六、附件和图片

公开文章使用的附件同样必须位于 Public 内。

正确:

Public/
├── article.md
└── attachments/
    └── test.jpg

Obsidian:

![[test.jpg]]

不要使用:

Vault/
├── Public/
│   └── article.md

└── test.jpg

虽然 Obsidian 可以在整个 Vault 中找到 test.jpg,但 Quartz 输入目录只有:

/vault/Public

因此无法访问 Public 外的附件。

建议在 Obsidian:

设置
→ 文件与链接
→ 新附件的默认位置

设置为当前笔记目录下的附件子目录。


七、部署 Quartz v5

使用官方 Quartz 镜像:

ghcr.io/jackyzha0/quartz:latest

创建:

/opt/obsidian/quartz/quartz.config.yaml

基础配置:

configuration:
  pageTitle: "LinVK"
  locale: zh-CN
  baseUrl: linvk.com

Quartz Compose:

services:
  quartz:
    image: ghcr.io/jackyzha0/quartz:latest
    container_name: obsidian-quartz
    restart: unless-stopped
 
    user: "0:0"
    working_dir: /usr/src/app
 
    mem_limit: 768m
    memswap_limit: 1g
 
    volumes:
      - "/opt/obsidian/vault:/vault:ro"
      - "/opt/obsidian/quartz/quartz.config.yaml:/usr/src/app/quartz.config.yaml:ro"
      - "/opt/1panel/www/sites/linvk.com:/site"
 
    entrypoint:
      - /bin/sh
      - -lc
 
    command:
      - exec npx quartz build -d /vault/Public -o /site/index --concurrency 1 --watch

其中:

-d /vault/Public

指定博客来源。

-o /site/index

指定生成目录。

--watch

持续监听 Public 中的文件变化。

--concurrency 1

限制构建并发,在低核心数服务器上可以减少瞬时负载。

启动后 Quartz 容器应长期保持:

Running

而不是一次性构建模式下的:

Exited (0)

八、为什么输出到 /site/index

不要直接这样挂载:

- /opt/1panel/www/sites/linvk.com/index:/output

然后:

-o /output

Quartz 构建时会清理输出目录。

Docker bind mount 本身不能被直接删除,因此可能出现:

EBUSY: resource busy or locked

正确方式是挂载父目录:

- /opt/1panel/www/sites/linvk.com:/site

再输出到普通子目录:

/site/index

对应:

/opt/1panel/www/sites/linvk.com/index

Quartz 可以正常删除和重新创建 index


九、创建博客首页

Public 根目录需要:

index.md

例如:

---
title: LinVK
description: LinVK 的个人笔记与技术记录
---
 
# LinVK
 
技术记录、项目笔记以及问题排查记录。
 
## 内容
 
- Linux
- Docker
- 网络
- 服务器
- 嵌入式
- AI
 
> 记录问题,也记录解决问题的过程。

Quartz 会生成:

index.html

对应:

https://linvk.com/

十、配置 1Panel 网站

在 1Panel 创建静态网站:

linvk.com

网站目录指向:

/opt/1panel/www/sites/linvk.com/index

配置 SSL 后即可由 OpenResty 提供 Quartz 生成的静态文件。


十一、配置 Clean URL

Quartz 生成的文件通常类似:

index.html
hello-world.html
test.html

但页面链接使用:

/
/hello-world
/test

因此 OpenResty 需要支持无 .html 后缀访问。

1Panel:

网站
→ linvk.com
→ 伪静态

配置:

location / {
    try_files $uri $uri.html $uri/ =404;
}
 
error_page 404 /404.html;
 
location = /404.html {
    internal;
}

此时:

/hello-world

会自动匹配:

/hello-world.html

浏览器地址仍然保持:

https://linvk.com/hello-world

十二、Quartz 自动更新流程

启用 --watch 后,不再需要每分钟执行 Quartz 构建任务。

完整流程:

Obsidian 编辑文章


Self-hosted LiveSync


CouchDB


livesync-cli


/opt/obsidian/vault/Public


Quartz --watch


重新生成 HTML


OpenResty


linvk.com

更新时间主要取决于 livesync-cli 的轮询周期。

如果:

--interval 30

通常保存 Obsidian 后几十秒内即可更新网站。


十三、Quartz 资源限制

Quartz 是 Node.js 应用。

--watch 模式会长期驻留内存,因此服务器需要保留足够的 RAM。

建议至少配置:

mem_limit: 768m
memswap_limit: 1g

同时观察:

free -h
cat /proc/pressure/memory
docker stats obsidian-quartz

正常状态应满足:

MemAvailable 有明显余量
Swap 不持续快速增长
memory PSI 接近 0

如果出现:

Swap 100%
kswapd0 高占用
memory PSI 接近 100%
load 快速升高

说明宿主机内存不足。

此时应该增加服务器内存,而不是继续提高 Quartz 的容器限制。


十四、磁盘空间

Docker、containerd、数据库和日志会持续消耗系统盘。

建议定期检查:

df -h /
docker system df
journalctl --disk-usage

尤其需要关注:

/var/lib/containerd
/opt/1panel/apps
/opt/1panel/backup
/opt/1panel/tmp

不要直接删除:

/var/lib/containerd/*
/var/lib/docker/*

这会破坏 Docker 镜像和容器状态。

Docker 构建缓存可以通过:

docker builder prune -af

清理。

APT 缓存:

apt clean

对于数据库服务器,建议系统盘长期保持至少:

15%~20%

可用空间。

CouchDB 在磁盘完全耗尽时可能出现:

ENOSPC
HTTP 500

因此不应让根分区长期处于 95% 以上。


十五、安全配置

以下内容不应直接暴露公网:

PostgreSQL 5432
CouchDB 5984
Redis 6379
MySQL 3306

数据库应监听:

127.0.0.1

或仅发布到 Docker 内部网络。

CouchDB 对外只保留:

443

obsidian.example.com

OpenResty

CouchDB

同时不要将以下内容放入:

Public/

包括:

  • SSH 私钥
  • API Key
  • Cookie
  • Token
  • 数据库密码
  • Cloudflare 凭据
  • LiveSync Setup URI
  • VPN 私钥
  • 未脱敏配置文件

十六、最终架构

┌────────────────────┐
│     Obsidian       │
│    PC / Android    │
└─────────┬──────────┘

          │ Self-hosted LiveSync

┌────────────────────┐
│      CouchDB       │
└─────────┬──────────┘

          │ livesync-cli

┌────────────────────┐
│       Vault        │
│                    │
│ Private   Public   │
│              │     │
└──────────────┼─────┘

               │ Quartz v5 --watch

┌────────────────────┐
│    Static HTML     │
└─────────┬──────────┘

          │ OpenResty

┌────────────────────┐
│     linvk.com      │
└────────────────────┘

整个系统中各组件职责:

组件作用
ObsidianMarkdown 编辑
Self-hosted LiveSync多设备同步
CouchDBLiveSync 数据存储
livesync-cliCouchDB 内容同步为真实 Vault
Public博客发布边界
Quartz v5Markdown 转静态网站
--watch自动监听 Public 变化
Docker服务运行环境
1Panel服务和网站管理
OpenRestyHTTPS、静态文件和 Clean URL

最终使用方式:

在 Obsidian 中写文章

移动到 Public

保存

自动同步

自动构建

博客更新

原始内容始终保持为:

Markdown + 图片 + 附件

即使以后更换静态网站生成器,也不影响 Obsidian 中的原始笔记。