Skip to content

Commit 12a9133

Browse files
committed
feat(comments): Artalk 部署配置化 — site.commentsProvider 驱动 + 镜像拆分
- Docker 镜像拆分 prod / prod-artalk(内置 Artalk 二进制)与 dev / dev-artalk, docker-compose 用 VANBLOG_IMAGE_TAG 选择镜像 - 内置 Artalk 是否启动改由持久化的 site.commentsProvider 决定,弃用 VANBLOG_ARTALK_ENABLED 环境变量开关 - /setup 首次初始化可选启用 Artalk:自动生成 artalk.yml 并创建管理员账号 - 新增 /api/vanblog/runtime/comments 就绪探针,entrypoint.prod.sh 据此拉起内置 Artalk - setup/status 暴露 capabilities.artalk(镜像是否带二进制),前端据此禁用选项 - Caddy 仅当 commentsProvider=artalk 时生成 /comments/* 反向代理路由 - SDK setup.complete 扩展 comments 字段;docs 新增 comments-system.md 架构总览, comments-artalk.md / configuration.md / faq.md / features / demo 同步更新
1 parent 2b6bf07 commit 12a9133

14 files changed

Lines changed: 482 additions & 62 deletions

File tree

Dockerfile

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -129,17 +129,10 @@ WORKDIR /app
129129
# Install Caddy + Node.js (for Astro SSR) + ca-certificates
130130
RUN apk add --no-cache caddy nodejs ca-certificates tzdata curl
131131

132-
# Artalk (self-hosted comment system) — optional sidecar, enabled at runtime
133-
# via VANBLOG_ARTALK_ENABLED=1. Downloaded at build time so the image is
134-
# reproducible. Override the version with --build-arg ARTALK_VERSION=vX.Y.Z.
135-
# NOTE: if Artalk changes its release asset naming, update the URL below.
136-
ARG ARTALK_VERSION=v2.10.0
137-
RUN mkdir -p /tmp/artalk && \
138-
curl -fsSL "https://github.com/ArtalkJS/Artalk/releases/download/${ARTALK_VERSION}/artalk_${ARTALK_VERSION}_linux_amd64.tar.gz" -o /tmp/artalk/artalk.tar.gz && \
139-
tar -xzf /tmp/artalk/artalk.tar.gz -C /tmp/artalk && \
140-
find /tmp/artalk -type f -name 'artalk' -exec mv {} /usr/local/bin/artalk \; && \
141-
chmod +x /usr/local/bin/artalk && \
142-
rm -rf /tmp/artalk
132+
# Artalk is provided by the separate prod-artalk target.
133+
# image intentionally has no Artalk binary; prod-artalk adds it below.
134+
135+
143136

144137
# Copy Go binary and the separately-built core schema artifact.
145138
COPY --from=go-build /pocketbase /usr/local/bin/vanblog
@@ -195,7 +188,18 @@ VOLUME ["/pb_data", "/data/caddy", "/data/artalk"]
195188

196189
ENTRYPOINT ["/entrypoint.sh"]
197190

198-
# --- Stage 6: DEV image (extends prod + full Node toolchain + source) ---
191+
# --- Stage 6: PROD image with bundled Artalk capability ---
192+
FROM prod AS prod-artalk
193+
ARG ARTALK_VERSION=v2.10.0
194+
RUN apk add --no-cache curl && \
195+
mkdir -p /tmp/artalk && \
196+
curl -fsSL "https://github.com/ArtalkJS/Artalk/releases/download/${ARTALK_VERSION}/artalk_${ARTALK_VERSION}_linux_amd64.tar.gz" -o /tmp/artalk/artalk.tar.gz && \
197+
tar -xzf /tmp/artalk/artalk.tar.gz -C /tmp/artalk && \
198+
find /tmp/artalk -type f -name 'artalk' -exec mv {} /usr/local/bin/artalk \; && \
199+
chmod +x /usr/local/bin/artalk && \
200+
rm -rf /tmp/artalk
201+
202+
# --- Stage 7: DEV image (plain prod capability) ---
199203
FROM prod AS dev
200204

201205
RUN apk add --no-cache npm git && npm install -g pnpm@latest-10
@@ -242,3 +246,25 @@ ENV VANBLOG_MODE=dev
242246
EXPOSE 80 443 4321 8080
243247

244248
ENTRYPOINT ["/entrypoint.sh"]
249+
250+
# Development image with bundled Artalk capability.
251+
FROM prod-artalk AS dev-artalk
252+
RUN apk add --no-cache npm git && npm install -g pnpm@latest-10
253+
COPY package.json pnpm-workspace.yaml pnpm-lock.yaml .npmrc /workspace/
254+
COPY sdk/ /workspace/sdk/
255+
COPY app/ /workspace/app/
256+
COPY packs/ /workspace/packs/
257+
COPY themes/ /workspace/themes/
258+
COPY scripts/ /workspace/scripts/
259+
COPY models.config.mjs /workspace/models.config.mjs
260+
COPY docs/ /workspace/docs/
261+
COPY AGENTS.md /workspace/AGENTS.md
262+
COPY .pi/ /workspace/.pi/
263+
COPY .agents/ /workspace/.agents/
264+
WORKDIR /workspace
265+
RUN pnpm install --frozen-lockfile
266+
COPY docker/entrypoint.dev.sh /entrypoint.sh
267+
RUN chmod +x /entrypoint.sh
268+
ENV VANBLOG_MODE=dev
269+
EXPOSE 80 443 4321 8080
270+
ENTRYPOINT ["/entrypoint.sh"]

app/src/pages/setup.astro

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,17 @@ if (alreadySetup) {
6262
<input name="passwordConfirm" type="password" required autocomplete="new-password" minlength="8"
6363
class="px-1.5 py-0.5" />
6464
</label>
65+
<fieldset class="border p-3 mt-3">
66+
<legend>评论系统(可选)</legend>
67+
<label class="flex items-center gap-2"><input name="commentsProvider" type="radio" value="disabled" checked /> 不启用 Artalk</label>
68+
<label class="flex items-center gap-2"><input name="commentsProvider" type="radio" value="artalk" /> 启用内置 Artalk</label>
69+
<div id="artalk-setup" class="hidden flex flex-col gap-2 mt-2">
70+
<input name="artalk_site" type="text" value="VanBlog" placeholder="Artalk Site 名" />
71+
<input name="artalk_email" type="email" autocomplete="email" placeholder="Artalk 管理员邮箱" />
72+
<input name="artalk_password" type="password" autocomplete="new-password" minlength="8" placeholder="Artalk 管理员密码(至少 8 位)" />
73+
<input name="artalk_password_confirm" type="password" autocomplete="new-password" minlength="8" placeholder="确认 Artalk 管理员密码" />
74+
</div>
75+
</fieldset>
6576
<input type="hidden" name="back" value={back} />
6677
<button type="submit" class="self-start px-3 py-1">创建管理员</button>
6778
<p id="setup-error" class="text-red-600 text-sm hidden"></p>
@@ -73,6 +84,19 @@ if (alreadySetup) {
7384
const form = document.getElementById('setup-form');
7485
const errEl = document.getElementById('setup-error');
7586
const statusEl = document.getElementById('setup-status');
87+
const artalkSetup = document.getElementById('artalk-setup');
88+
const providerInputs = form.querySelectorAll('[name="commentsProvider"]');
89+
const setupStatus = self.vanblog?.pb?.vanblog?.setup;
90+
setupStatus?.status?.().then((status) => {
91+
const artalk = form.querySelector('[value="artalk"]');
92+
if (!status.capabilities?.artalk && artalk) {
93+
artalk.disabled = true;
94+
artalk.parentElement.title = '当前镜像不包含 Artalk,请使用 prod-artalk 镜像';
95+
}
96+
}).catch(() => {});
97+
providerInputs.forEach((input) => input.addEventListener('change', () => {
98+
artalkSetup.classList.toggle('hidden', input.checked && input.value === 'artalk');
99+
}));
76100

77101
form.addEventListener('submit', async (e) => {
78102
e.preventDefault();
@@ -95,6 +119,13 @@ if (alreadySetup) {
95119
email: fd.get('email'),
96120
password: fd.get('password'),
97121
passwordConfirm: fd.get('passwordConfirm'),
122+
comments: {
123+
provider: fd.get('commentsProvider') || 'disabled',
124+
artalkSite: fd.get('artalk_site') || 'VanBlog',
125+
artalkEmail: fd.get('artalk_email') || '',
126+
artalkPassword: fd.get('artalk_password') || '',
127+
artalkPasswordConfirm: fd.get('artalk_password_confirm') || '',
128+
},
98129
});
99130
if (r.ok) {
100131
statusEl.textContent = '✓ 管理员创建成功,正在跳转登录…';

docker-compose.yml

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626

2727
services:
2828
vanblog:
29-
image: ghcr.io/cornworld/vanblog:prod-edge
29+
# 选择 prod(不含 Artalk) 或 prod-artalk(含 Artalk 能力,默认仍关闭):
30+
image: ghcr.io/cornworld/vanblog:${VANBLOG_IMAGE_TAG:-prod}
3031
# 本地构建: build: { context: ., target: prod }
3132
restart: unless-stopped
3233
ports:
@@ -39,16 +40,21 @@ services:
3940
- caddy_data:/data/caddy
4041
- pack_data:/var/lib/vanblog/packs
4142
- themes_data:/var/lib/vanblog/themes
42-
# Artalk 内置 sidecar 数据(启用 VANBLOG_ARTALK_ENABLED=1 时持久化评论):
43+
# Artalk 数据(仅 prod-artalk 镜像启用内置 Artalk 时使用):
4344
- artalk_data:/data/artalk
45+
# 若要自行维护配置文件,可取消注释(会覆盖自动生成):
46+
# - ./artalk.yml:/data/artalk/artalk.yml:ro
4447
environment:
4548
- VANBLOG_EMAIL=${VANBLOG_EMAIL:-admin@example.com}
4649
- VANBLOG_CADDY_LOG_LEVEL=${VANBLOG_CADDY_LOG_LEVEL:-warn}
4750
- VANBLOG_PACKS_DIR=/var/lib/vanblog/packs
4851
- VANBLOG_THEMES_DIR=/var/lib/vanblog/themes
4952
# --- Artalk 评论系统 ---
50-
# 内置 sidecar(单容器,默认推荐):
51-
# - VANBLOG_ARTALK_ENABLED=1
53+
# 选择 prod-artalk 后,在 /setup 向导中选择是否启用并填写 Artalk 账号。
54+
# 这里只保留外部 Artalk upstream;它不负责启用状态:
55+
# - VANBLOG_ARTALK_UPSTREAM=artalk:23366
56+
# SSO 桥接开关(可选,默认关闭):
57+
# - VANBLOG_COMMENTS_SSO_ENABLED=1
5258
# 外部 Artalk 容器(compose 多容器,配合下面 artalk 服务):
5359
# - VANBLOG_ARTALK_UPSTREAM=artalk:23366
5460
# HTTP_ONLY 模式:外置反代终止 TLS(取消注释即生效):
@@ -58,7 +64,7 @@ services:
5864
# - PB_URL=http://127.0.0.1:8090
5965

6066
# --- Artalk 评论系统(compose 多容器模式,可选)---
61-
# 默认推荐内置 sidecar(vanblog 服务设 VANBLOG_ARTALK_ENABLED=1)
67+
# 默认推荐 prod-artalk 镜像;是否启用内置 Artalk 在 /setup 向导中选择
6268
# 若要 Artalk 独立容器(独立升级/解耦生命周期),取消下面注释,
6369
# 并在 vanblog 服务里设 VANBLOG_ARTALK_UPSTREAM=artalk:23366。
6470
# artalk:

docker/entrypoint.prod.sh

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,26 @@ vanblog serve "$@" &
8787
PB_PID=$!
8888
wait_for "http://127.0.0.1:8090/api/health" "PocketBase" 30 || exit 1
8989

90-
# 3.5. Start Artalk comment sidecar (optional; enabled via VANBLOG_ARTALK_ENABLED=1)
91-
if [ "${VANBLOG_ARTALK_ENABLED}" = "1" ] || [ "${VANBLOG_ARTALK_ENABLED}" = "true" ]; then
92-
echo "[vanblog] starting Artalk comment sidecar (port 23366)..."
93-
mkdir -p /data/artalk
94-
(cd /data/artalk && exec env ATK_HOST=127.0.0.1 ATK_PORT=23366 artalk server) &
95-
ARTALK_PID=$!
96-
wait_for "http://127.0.0.1:23366/" "Artalk" 30 || exit 1
90+
# 3.5. Start the bundled Artalk only when the persisted site provider is artalk.
91+
# The prod image may not contain the binary; prod-artalk does.
92+
if command -v artalk >/dev/null 2>&1; then
93+
COMMENTS_PROVIDER=$(wget -q -O - -T 2 "http://127.0.0.1:8090/api/vanblog/runtime/comments" 2>/dev/null || echo '{"provider":"disabled"}')
94+
case "$COMMENTS_PROVIDER" in
95+
*'"provider":"artalk"'*)
96+
echo "[vanblog] starting bundled Artalk (port 23366)..."
97+
ARTALK_DIR="/data/artalk"
98+
ARTALK_CONFIG="${ARTALK_DIR}/artalk.yml"
99+
mkdir -p "$ARTALK_DIR"
100+
if [ ! -f "$ARTALK_CONFIG" ]; then
101+
echo "[vanblog] Artalk config not found, generating default: $ARTALK_CONFIG"
102+
artalk gen config "$ARTALK_CONFIG"
103+
fi
104+
(cd "$ARTALK_DIR" && exec env ATK_HOST=127.0.0.1 ATK_PORT=23366 artalk -c "$ARTALK_CONFIG" server) &
105+
ARTALK_PID=$!
106+
wait_for "http://127.0.0.1:23366/" "Artalk" 30 || exit 1
107+
;;
108+
*) echo "[vanblog] bundled Artalk is installed but disabled" ;;
109+
esac
97110
fi
98111

99112
# 4. Start Theme Host (replaces direct Astro SSR)

docs/comments-artalk.md

Lines changed: 76 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
# Artalk 评论系统集成(自托管 + 同源)
22

3+
> 本文是 Artalk 的**部署操作手册**。评论系统的架构与集成总览(conf 数据流、鉴权边界、provider 模型)见 [`comments-system.md`](./comments-system.md)
4+
35
VanBlog 内置 Artalk 评论系统的支持,提供两种部署方式,**都走 Caddy 同源出口 `/comments/*`**,无跨域、无独立 TLS 证书、无额外端口暴露。
46

5-
- **内置 sidecar(单容器,默认推荐**:Artalk 进程跑在 VanBlog 容器内,一个开关启用
7+
- **内置 Artalk(prod-artalk 镜像**镜像带有 Artalk 二进制;是否启动在首次 `/setup` 向导中选择,默认关闭
68
- **compose 多容器(external 接入)**:Artalk 作为独立容器(官方镜像),VanBlog 反代接入。
79

810
两种方式共用同一套前端配置和 Caddy 路由生成逻辑。
@@ -19,7 +21,7 @@ Artalk 二进制已内置在 prod 镜像里(构建期下载,`--build-arg ART
1921

2022
```yaml
2123
environment:
22-
- VANBLOG_ARTALK_ENABLED=1
24+
- 选择 `prod-artalk` 镜像,并在首次 `/setup` 向导中启用内置 Artalk
2325
```
2426
2527
同时确认 vanblog 服务已挂载 Artalk 数据卷(`docker-compose.yml` 里默认已带):
@@ -66,7 +68,7 @@ services:
6668
- VANBLOG_ARTALK_UPSTREAM=artalk:23366
6769
```
6870

69-
> 注意:`VANBLOG_ARTALK_UPSTREAM` 优先级高于 `VANBLOG_ARTALK_ENABLED`。外部容器模式下**不要**同时设 `VANBLOG_ARTALK_ENABLED=1`(那会额外启动内置 sidecar)
71+
> 注意:`VANBLOG_ARTALK_UPSTREAM` 只表示外部上游地址,不负责启用 Artalk。内置 Artalk 是否启动由 `site.commentsProvider` 决定
7072

7173
---
7274

@@ -83,10 +85,9 @@ services:
8385

8486
## Artalk 管理员账号初始化
8587

86-
Artalk 首次部署后不会自动创建管理员。两种方式任选:
88+
Artalk 管理员不通过命令行初始化。选择 `prod-artalk` 镜像后,首次打开 `/setup` 时可以直接选择启用 Artalk,并填写 Artalk 管理员 email/password;VanBlog 会生成配置并完成账号初始化。
8789

88-
1. **评论框初始化(推荐)**:打开任意文章页评论框,输入管理员用户名和邮箱并提交一条评论,评论框右下角会出现「控制台」按钮,进入 Artalk 管理后台。
89-
2. **CLI**(内置 sidecar):`docker exec -it <容器> artalk admin`。
90+
账号由 `/setup` 向导创建。Artalk 管理员 email 建议与 VanBlog 管理员 email 保持一致,这样后续 SSO 免二次登录可以按 email 映射为 Artalk 管理员。
9091

9192
---
9293

@@ -103,8 +104,76 @@ Artalk 首次部署后不会自动创建管理员。两种方式任选:
103104

104105
---
105106

107+
## SSO 免二次登录(可选)
108+
109+
VanBlog 提供与 Artalk 的单点登录桥接:博主在 VanBlog 后台点「打开评论管理」,无需再次输入 Artalk 账号密码,直接以管理员身份进入评论管理。
110+
111+
### 原理
112+
113+
复用 Artalk 原生的 `POST /api/v2/sso/exchange` 换票端点:VanBlog 签发一个短期(60 秒)随机 token,Artalk 拿它去 VanBlog 的 `/userinfo` 端点换出登录态。**VanBlog 不持有 Artalk 凭证**,只签发自己控制的短期 token。
114+
115+
### 1. 启用 VanBlog 侧 SSO
116+
117+
在 `docker-compose.yml` 的 `vanblog` 服务环境变量里加:
118+
119+
```yaml
120+
environment:
121+
- VANBLOG_COMMENTS_SSO_ENABLED=1
122+
```
123+
124+
默认关闭;不设该变量时两个 SSO 端点(`/api/vanblog/comments-sso/*`)不会注册,零攻击面。
125+
126+
### 2. 生成并配置 Artalk
127+
128+
Artalk 提供 `gen config` 命令主动生成默认配置文件:
129+
130+
```bash
131+
# 在宿主机执行,直接生成当前目录的配置
132+
artalk gen config ./artalk.yml
133+
134+
# 如果文件已存在,使用 -f 强制覆盖(会丢失已有配置)
135+
artalk gen config ./artalk.yml -f
136+
```
137+
138+
也可以在内置 sidecar 的数据卷中生成:
139+
140+
```bash
141+
docker compose exec vanblog artalk gen config /data/artalk/artalk.yml
142+
```
143+
144+
然后编辑 `artalk.yml`,至少配置 SSO:
145+
146+
```yaml
147+
auth:
148+
enabled: true
149+
sso:
150+
enabled: true
151+
issuer: "https://<你的域名>/api/vanblog/comments-sso"
152+
```
153+
154+
管理员账号使用 Artalk 自带命令创建或修改,不需要把密码写进 `artalk.yml`:
155+
156+
```bash
157+
artalk admin \
158+
--name 博主 \
159+
--email <你的博主邮箱> \
160+
--password '<一个初始密码>'
161+
```
162+
163+
`--email` **必须与 VanBlog 管理员账号的邮箱一致**。SSO 换票按邮箱找到 Artalk 用户;邮箱一致时,换票用户才能获得 Artalk 管理员身份。
164+
165+
- `issuer` 的域名必须与 VanBlog 的 `baseUrl` 一致,且为 HTTPS(生产环境)。
166+
- Artalk 会请求 `{issuer}/userinfo`,即 VanBlog 的 `/api/vanblog/comments-sso/userinfo`。
167+
- `gen config` 默认不会覆盖已有文件;只有明确传 `-f` 才会覆盖。
168+
169+
### 3. 使用
170+
171+
进入 VanBlog 后台 → 站点配置 → 评论,选 `artalk` 并填好 Server 后,点「打开评论管理(免二次登录)」。系统会换票并跳转到博客首页,评论框自动识别管理员身份,右下角出现「控制台」按钮即可进入管理。
172+
173+
---
174+
106175
## 架构说明
107176

108177
- **Caddy 路由生成**:`vault/internal/caddy/config_builder.go` 的 `buildFullRouteTable` 在系统 API 路由后、用户规则前插入 `/comments/*` 反向代理路由(当 `BuildOpts.ArtalkUpstream` 非空时)。
109-
- **上游地址**:`vault/internal/caddy/bootstrap.go` 读环境变量 `VANBLOG_ARTALK_UPSTREAM`;为空时若 `VANBLOG_ARTALK_ENABLED=1` 则回退到 `127.0.0.1:23366`。
178+
- **上游地址**:`vault/internal/caddy/bootstrap.go` 仅在 `site.commentsProvider=artalk` 时生成 Artalk 上游;配置 `VANBLOG_ARTALK_UPSTREAM` 时使用外部实例,否则回退到 `127.0.0.1:23366`。
110179
- **同源前缀剥离**:Artalk 本身不支持 base path(以 `/` 为根),所以系统路由用 `rewrite strip_path_prefix` 剥掉 `/comments` 前缀后再反向代理。`site.routing` 的 proxy 规则也提供 `stripPathPrefix` 字段,可复用于任意不支持 base path 的上游。

0 commit comments

Comments
 (0)