架构概览
Obsidian 编辑笔记
↓ git push
CNB 仓库(代码托管 + CI/CD)
↓ 流水线自动触发
克隆 Quartz → npm install → npx quartz build
↓ rsync over SSH(通过 FRP 隧道)
本地服务器(内网,1Panel + OpenResty)
↓ OpenResty 反代静态文件
↓ FRP 穿透到公网
云服务器(Nginx Proxy Manager)
↓ 域名解析
用户访问
环境信息
| 组件 | 说明 |
|---|---|
| 本地服务器 | 内网机器,1Panel 面板,OpenResty |
| 云服务器 | <云服务器IP>,Nginx Proxy Manager |
| 代码托管 | CNB(cnb.cool) |
| 内网穿透 | FRP |
| 静态站点生成 | Quartz(Node.js) |
| 笔记编辑 | Obsidian |
一、本地服务器准备
1.1 安装 rsync
apt update && apt install -y rsync1.2 确保 SSH 服务运行
systemctl status sshd
# 如果没装
apt install openssh-server
systemctl enable --now sshd1.3 生成部署专用 SSH 密钥
ssh-keygen -t ed25519 -f ~/.ssh/cnb_deploy -C "cnb-deploy" -N ""生成两个文件:
~/.ssh/cnb_deploy— 私钥(放到 CNB 密钥仓库)~/.ssh/cnb_deploy.pub— 公钥(放到服务器上)
1.4 配置公钥认证(使用 root 用户)
cat ~/.ssh/cnb_deploy.pub >> /root/.ssh/authorized_keys
chmod 600 /root/.ssh/authorized_keys1.5 确保 SSH 允许 root 登录
编辑 /etc/ssh/sshd_config:
PermitRootLogin yes
systemctl restart sshd1.6 创建静态文件目录
使用 1Panel 创建站点后,静态文件目录为:
/opt/1panel/www/sites/quartz/index
二、FRP 配置
2.1 暴露 SSH 端口(用于 rsync 部署)
在 FRP 客户端配置中添加:
[[proxies]]
name = "deploy-ssh"
type = "tcp"
localIP = "127.0.0.1"
localPort = 22
remotePort = 60222.2 暴露 HTTP 端口(用于 Web 访问)
[[proxies]]
name = "quartzTCP"
type = "tcp"
localIP = "127.0.0.1"
localPort = 80
remotePort = 40002.3 重启 FRP 客户端
systemctl restart frpc2.4 云服务器防火墙放通端口
ufw allow 6022/tcp
ufw allow 4000/tcp2.5 验证 SSH 隧道
ssh -i ~/.ssh/cnb_deploy -p 6022 root@<云服务器IP>三、CNB 配置
3.1 创建密钥仓库
- 在 CNB 组织
weixiao.space下创建私有仓库secrets - 进入仓库 → 设置 → 开启「密钥仓库」功能
- 在仓库根目录创建
env.yml文件:
SSH_PRIVATE_KEY: |
-----BEGIN OPENSSH PRIVATE KEY-----
(粘贴 ~/.ssh/cnb_deploy 私钥的完整内容)
(每行前面必须有两个空格缩进)
-----END OPENSSH PRIVATE KEY-----获取私钥内容:
cat ~/.ssh/cnb_deploy3.2 创建 Quartz 笔记仓库
在 CNB 组织下创建仓库存放笔记,仓库结构:
仓库根目录/
├── .cnb.yml ← 流水线配置
├── .quartz/ ← Quartz 自定义配置目录
│ ├── quartz.config.ts ← 站点配置
│ ├── quartz.layout.ts ← 布局配置
│ ├── custom.scss ← 自定义样式
│ └── TwikooComments.tsx ← 自定义组件(评论等)
├── index.md ← 首页(必须有)
├── 笔记1.md
├── 笔记2.md
└── 子目录/
└── 笔记3.md
注意: 仓库根目录必须有
index.md文件,否则 Quartz 不会生成index.html首页。
3.3 创建 .cnb.yml
main:
push:
- name: Build and Deploy Quartz
docker:
image: node:22
volumes:
- /root/.npm
imports:
- https://cnb.cool/weixiao.space/secrets/-/blob/main/env.yml
stages:
- name: Setup Quartz and Build
script: |
git clone https://github.com/jackyzha0/quartz.git /tmp/quartz
cd /tmp/quartz
npm install
# 复制笔记到 content
rm -rf content
cp -r /workspace content
# 复制 .quartz 目录中的自定义配置
cp /workspace/.quartz/quartz.config.ts .
cp /workspace/.quartz/quartz.layout.ts .
cp /workspace/.quartz/custom.scss quartz/styles/custom.scss
cp /workspace/.quartz/TwikooComments.tsx quartz/components/TwikooComments.tsx
npx quartz build
- name: Deploy to server
script: |
mkdir -p ~/.ssh
echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
apt-get update && apt-get install -y rsync openssh-client
rsync -avz --delete \
-e "ssh -p 6022 -o StrictHostKeyChecking=no" \
/tmp/quartz/public/ root@<云服务器IP>:/opt/1panel/www/sites/quartz/index/3.4 imports 格式说明
https://cnb.cool / weixiao.space / secrets / -/blob/main / env.yml
协议 组织名 仓库名 固定格式 文件路径
对应关系:
| GitHub Actions | CNB |
|---|---|
| Settings → Secrets 添加变量 | 密钥仓库的 env.yml 里写变量 |
${{ secrets.SSH_PRIVATE_KEY }} | $SSH_PRIVATE_KEY |
四、1Panel 本地站点配置
4.1 OpenResty 站点配置
通过 1Panel 创建静态站点 quartz,配置文件参考:
server {
listen 80;
listen 4000;
server_name 127.0.0.1 <本地服务器IP> weixiao.space <云服务器IP>;
index index.php index.html index.htm default.php default.htm default.html;
access_log /www/sites/quartz/log/access.log main;
error_log /www/sites/quartz/log/error.log;
location ~ ^/(\.user.ini|\.htaccess|\.git|\.env|\.svn|\.project|LICENSE|README.md) {
return 404;
}
location ^~ /.well-known/acme-challenge {
allow all;
root /usr/share/nginx/html;
}
root /www/sites/quartz/index;
error_page 404 /404.html;
}注意:
server_name需要包含你的域名,否则通过域名访问时 OpenResty 无法匹配。
4.2 静态文件目录映射
1Panel 中站点的静态文件目录为:
宿主机: /opt/1panel/www/sites/quartz/index
容器内: /www/sites/quartz/index
rsync 部署时目标路径使用宿主机路径。
五、云服务器 Nginx Proxy Manager 配置
5.1 关键问题:Docker 网络
NPM 通常运行在 Docker 容器中,容器内的 127.0.0.1 指向容器自身而非宿主机。因此需要使用 Docker 网关 IP。
查看 Docker 网关地址:
ip addr show docker0
# 通常为 172.17.0.15.2 添加 Proxy Host
- 进入 NPM 管理界面(
:81) - 添加 Proxy Host:
- Domain Names:
weixiao.space(你的域名) - Forward Hostname:
172.17.0.1(Docker 网关 IP,不是 127.0.0.1) - Forward Port:
4000
- Domain Names:
- SSL 标签可配置 Let’s Encrypt 证书
5.3 域名解析
将域名 A 记录解析到云服务器 IP <云服务器IP>。
六、Quartz 自定义配置
6.1 quartz.config.ts
在 .quartz/quartz.config.ts 中配置站点标题、字体、主题颜色等:
import { QuartzConfig } from "./quartz/cfg"
import * as Plugin from "./quartz/plugins"
const config: QuartzConfig = {
configuration: {
pageTitle: "微笑的笔记",
enableSPA: true,
enablePopovers: true,
analytics: null,
locale: "zh-CN",
baseUrl: "weixiao.space",
ignorePatterns: ["private", "templates", ".obsidian", ".quartz"],
defaultDateType: "modified",
theme: {
fontOrigin: "googleFonts",
cdnCaching: true,
typography: {
header: "Noto Serif SC", // 衬线标题字体
body: "LXGW WenKai", // 霞鹜文楷正文字体(Google Fonts 自动加载)
code: "JetBrains Mono", // 代码字体
},
// 温暖文艺主题配色
colors: {
lightMode: {
light: "#FAF6F0", // 奶油色背景
lightgray: "#E8E0D8", // 暖石灰边框
gray: "#B8A99A", // 暖灰褐
darkgray: "#5C4F43", // 深暖棕正文
dark: "#3A302A", // 胡桃色标题
secondary: "#B85C3C", // 陶土色链接
tertiary: "#8B9E7C", // 鼠尾草绿高亮
highlight: "rgba(184, 92, 60, 0.12)",
textHighlight: "#F5D98E88", // 琥珀金高亮
},
darkMode: {
light: "#1E1A17", // 暖黑胡桃
lightgray: "#3A332D", // 暖深棕边框
gray: "#6B5F54", // 暖灰棕
darkgray: "#D4CABC", // 暖羊皮纸文字
dark: "#EDE6DB", // 暖象牙标题
secondary: "#D4845C", // 柔化陶土
tertiary: "#A3B592", // 亮鼠尾草绿
highlight: "rgba(212, 132, 92, 0.15)",
textHighlight: "#D4A04488", // 琥珀金暗色
},
},
},
},
plugins: {
transformers: [
Plugin.FrontMatter(),
Plugin.CreatedModifiedDate({
priority: ["frontmatter", "git", "filesystem"],
}),
Plugin.SyntaxHighlighting(),
Plugin.ObsidianFlavoredMarkdown({ enableInHtmlEmbed: false }),
Plugin.GitHubFlavoredMarkdown(),
Plugin.TableOfContents(),
Plugin.CrawlLinks({ markdownLinkResolution: "shortest" }),
Plugin.Description(),
Plugin.Latex({ renderEngine: "katex" }),
],
filters: [Plugin.RemoveDrafts()],
emitters: [
Plugin.AliasRedirects(),
Plugin.ComponentResources(),
Plugin.ContentPage(),
Plugin.FolderPage(),
Plugin.TagPage(),
Plugin.ContentIndex(),
Plugin.Assets(),
Plugin.Static(),
Plugin.NotFoundPage(),
],
},
}
export default config6.2 custom.scss
在 .quartz/custom.scss 中覆盖 Quartz 默认样式。流水线会将它复制到 quartz/styles/custom.scss,编译时在 base.scss 之后加载,因此同选择器的规则会覆盖默认值。
最简配置只需引入基础样式即可:
@use "./base.scss";如需自定义排版、引用块、代码块等样式,直接在后面追加 CSS/SCSS 规则。
6.3 常用自定义项
| 改什么 | 在哪改 |
|---|---|
| 站点标题 | quartz.config.ts → pageTitle |
| 语言 | quartz.config.ts → locale |
| 域名 | quartz.config.ts → baseUrl |
| 字体 | quartz.config.ts → typography 中的 header / body / code |
| 主题颜色 | quartz.config.ts → colors 中的 lightMode / darkMode |
| 忽略文件夹 | quartz.config.ts → ignorePatterns |
| 链接解析 | quartz.config.ts → CrawlLinks 的 markdownLinkResolution(shortest 兼容 Obsidian) |
| 布局/组件 | quartz.layout.ts → 调整页面布局、添加自定义组件 |
| 排版/组件样式 | custom.scss → 追加 CSS/SCSS 规则覆盖默认样式 |
| 评论系统 | 参考 Quartz 集成 Twikoo 评论系统 |
七、日常使用流程
# 1. 在 Obsidian 中编辑笔记
# 2. 推送到 CNB
git add .
git commit -m "update notes"
git push
# 3. CNB 自动触发流水线:构建 → 部署
# 4. 等待约 1-2 分钟,访问域名查看更新八、排错清单
| 问题 | 排查方法 |
|---|---|
| 流水线找不到密钥仓库 | 确认仓库已开启「密钥仓库」功能,确认 imports 路径带 https:// |
| npm install 失败 | 用 npm install 而非 npm ci(后者需要 package-lock.json) |
| 找不到 package.json | 确认流水线中先 clone Quartz 再 cd 到 Quartz 目录 |
| rsync 远程找不到命令 | 本地服务器安装 rsync:apt install -y rsync |
| SSH 连接被拒 | 检查 FRP 隧道、SSH 配置、密钥格式 |
| 403 Forbidden | 检查文件权限 chmod -R 755 |
| 404 无首页 | 确保仓库根目录有 index.md |
| NPM 502 Bad Gateway | Docker 容器内 127.0.0.1 指向容器自身,改用 Docker 网关 IP(172.17.0.1) |
| Apache 默认页面 | systemctl stop apache2 && systemctl disable apache2 |