Files
blog/deploy/editor-api/bootstrap.sh
T
zqlit c217d20c30 feat(editor): 在线编辑文章(Worker 前端 + 轻量 docker 后端)
后端(新增 editor-api/,零 npm 依赖,只用 node 内置模块):
- 只做文章相关:列表/读取/新建/保存/删除/图片上传/git 状态·发布·同步
- front matter 往返保真:未改动的块按字节照抄,CRLF/块标量/引号写法都不动
- 列表用目录名当 id,slug 撞名不再静默改错文件(返回 409 列候选)
- 鉴权只有一条路:X-Editor-Token(只存在 Worker 侧,浏览器拿不到)
- 端口只绑 127.0.0.1,由宿主机 nginx 反代出去

部署(新增 deploy/editor-api/bootstrap.sh):
- 一条命令在新机器上完成 克隆仓库→写 .env→起容器→健康检查
- 状态全在两个目录(/srv/blog 仓库工作区 + /srv/editor-api 配置),
  迁移 = 复制目录或在新机重跑本脚本,容器本身无状态

Cloudflare Worker 侧(blog-admin):
- src/routes/editor.ts:鉴权 + 反代,浏览器只跟 Worker 说话
- 管理面板新增「文章编辑」:两栏布局 + 快捷插入面板(13 项短代码,
  与 write-server 的 ShortcutPanel 一致)+ 底部 草稿/保存/发布
- 系统设置改为 schema 驱动的表单,且**以运行时实际生效的配置为准**
  (frontend_conf/captcha/moderator/ip_region/site_default + KV human_check),
  修复「表单显示一套、评论系统跑另一套」的脱节问题

验证:tsc 0 错;front matter 往返 130/130;保存往返 130/130;
API e2e 44/44;无头 Chrome UI e2e 14/14
2026-10-04 21:16:06 +08:00

160 lines
6.4 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# =====================================================================
# editor-api 一键部署 / 迁移脚本
# =====================================================================
# 用途:在一台全新机器上(或从快过期的机器上迁走)用一条命令把
# 「在线编辑文章」的后端跑起来。所有状态都在两个目录里:
# $BLOG_DIR = 博客仓库的 git 工作区(内容就在这里)
# $APP_DIR = compose + .env(只有这一个文件是手写的)
# 迁移 = 复制这两个目录 / 或在新机器上重跑本脚本。
#
# 用法:
# CNB_TOKEN=xxx EDITOR_TOKEN=yyy bash bootstrap.sh
#
# 必填环境变量:
# CNB_TOKEN CNB 访问令牌(用户名固定 cnb)。只用于 git clone 与 push。
# EDITOR_TOKEN 与 Cloudflare Worker 里 EDITOR_TOKEN **完全相同**的共享令牌
# (Worker 转发时会带上,后端用它鉴权;浏览器永远看不到)
#
# 可选环境变量(都有默认值):
# CNB_URL 默认 https://cnb.cool/zqlit/blog.git
# GH_URL 默认 git@github.com:zqlit/blog.git(配了 GH_SSH_KEY 才会推它)
# GH_SSH_KEY 可选:GitHub 部署私钥的**内容**。给了才会把 gh 加进推送远端,
# 否则 PUSH_REMOTES 自动降级成只推 origin(CNB 主仓)
# BLOG_DIR /srv/blog 仓库工作区
# APP_DIR /srv/editor-api compose + .env
# TRASH_DIR /srv/editor-trash 删除文章的回收目录(故意放在仓库外)
# BIND_PORT 8017 只绑 127.0.0.1
# PUSH_REMOTES origin,gh
# GIT_AUTHOR_NAME / GIT_AUTHOR_EMAIL / BLOG_BASE
# =====================================================================
set -euo pipefail
say() { printf '\033[1;36m==> %s\033[0m\n' "$*"; }
die() { printf '\033[1;31m!! %s\033[0m\n' "$*" >&2; exit 1; }
: "${CNB_TOKEN:?必须设置 CNB_TOKEN(CNB 访问令牌,用户名固定 cnb)}"
: "${EDITOR_TOKEN:?必须设置 EDITOR_TOKEN(与 Cloudflare Worker 里的一致)}"
CNB_URL="${CNB_URL:-https://cnb.cool/zqlit/blog.git}"
GH_URL="${GH_URL:-git@github.com:zqlit/blog.git}"
BLOG_DIR="${BLOG_DIR:-/srv/blog}"
APP_DIR="${APP_DIR:-/srv/editor-api}"
TRASH_DIR="${TRASH_DIR:-/srv/editor-trash}"
BIND_PORT="${BIND_PORT:-8017}"
GIT_AUTHOR_NAME="${GIT_AUTHOR_NAME:-blog-editor}"
GIT_AUTHOR_EMAIL="${GIT_AUTHOR_EMAIL:-editor@usj.cc}"
BLOG_BASE="${BLOG_BASE:-}"
PUSH_REMOTES="${PUSH_REMOTES:-origin,gh}"
# 没有 GitHub 私钥就只推主仓 —— 宁可少推一个备份,也不要让每次发布都报错
if [ -z "${GH_SSH_KEY:-}" ]; then
PUSH_REMOTES="origin"
fi
command -v docker >/dev/null || die "没有 docker"
docker compose version >/dev/null 2>&1 || die "没有 docker compose"
say "1/5 准备工作目录"
mkdir -p "$BLOG_DIR" "$APP_DIR" "$TRASH_DIR"
# compose 的 volume 必须写绝对路径,这里统一转成绝对路径
BLOG_DIR="$(cd "$BLOG_DIR" && pwd)"
APP_DIR="$(cd "$APP_DIR" && pwd)"
TRASH_DIR="$(cd "$TRASH_DIR" && pwd)"
say "2/5 拉取/更新博客仓库 → $BLOG_DIR"
AUTH_URL="$(printf '%s' "$CNB_URL" | sed -E "s#^https://#https://cnb:${CNB_TOKEN}@#")"
if [ -d "$BLOG_DIR/.git" ]; then
git -C "$BLOG_DIR" remote set-url origin "$AUTH_URL"
git -C "$BLOG_DIR" fetch origin main
git -C "$BLOG_DIR" checkout main
git -C "$BLOG_DIR" reset --hard origin/main
else
git clone --branch main "$AUTH_URL" "$BLOG_DIR"
fi
# gh 备份远端:只有给了私钥才配
if [ -n "${GH_SSH_KEY:-}" ]; then
install -d -m 700 ~/.ssh
printf '%s\n' "$GH_SSH_KEY" > ~/.ssh/blog_editor_github
chmod 600 ~/.ssh/blog_editor_github
ssh-keyscan -t rsa,ed25519 github.com >> ~/.ssh/known_hosts 2>/dev/null || true
git -C "$BLOG_DIR" remote remove gh 2>/dev/null || true
git -C "$BLOG_DIR" remote add gh "$GH_URL"
git -C "$BLOG_DIR" config core.sshCommand "ssh -i ~/.ssh/blog_editor_github -o StrictHostKeyChecking=accept-new"
fi
# 容器里要能 commit —— 顺手把身份写进仓库配置(不改全局)
git -C "$BLOG_DIR" config user.name "$GIT_AUTHOR_NAME"
git -C "$BLOG_DIR" config user.email "$GIT_AUTHOR_EMAIL"
say "3/5 写入 $APP_DIR/.env"
umask 077
# 用 printf 而不是 heredoc:令牌里若含 $ 或反引号,heredoc 会被 shell 展开
{
printf 'EDITOR_TOKEN=%s\n' "$EDITOR_TOKEN"
printf 'BLOG_DIR=%s\n' "$BLOG_DIR"
printf 'TRASH_DIR=%s\n' "$TRASH_DIR"
printf 'BIND_PORT=%s\n' "$BIND_PORT"
printf 'PUSH_REMOTES=%s\n' "$PUSH_REMOTES"
printf 'GIT_AUTHOR_NAME=%s\n' "$GIT_AUTHOR_NAME"
printf 'GIT_AUTHOR_EMAIL=%s\n' "$GIT_AUTHOR_EMAIL"
printf 'BLOG_BASE=%s\n' "$BLOG_BASE"
} > "$APP_DIR/.env"
say "4/5 生成 compose 并启动容器"
cat > "$APP_DIR/docker-compose.yml" <<EOF
# 本文件由 bootstrap.sh 生成 —— 想改配置请改 bootstrap.sh 或 .env 后重跑
services:
editor-api:
build: $BLOG_DIR/editor-api
image: editor-api:local
container_name: editor-api
restart: unless-stopped
ports:
- "127.0.0.1:$BIND_PORT:8017"
environment:
EDITOR_TOKEN: \${EDITOR_TOKEN:?}
BLOG_ROOT: /blog
TRASH_DIR: /app/trash
BIND_HOST: 0.0.0.0
GIT_BRANCH: main
PUSH_REMOTES: \${PUSH_REMOTES:-origin}
GIT_AUTHOR_NAME: \${GIT_AUTHOR_NAME:-blog-editor}
GIT_AUTHOR_EMAIL: \${GIT_AUTHOR_EMAIL:-editor@usj.cc}
BLOG_BASE: \${BLOG_BASE:-}
MAX_UPLOAD_MB: "20"
GIT_PATHS: content,static
volumes:
- \$BLOG_DIR:/blog
- \$TRASH_DIR:/app/trash
healthcheck:
test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:8017/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
EOF
cd "$APP_DIR"
docker compose up -d --build
say "5/5 健康检查"
for i in $(seq 1 30); do
if curl -fsS -H "X-Editor-Token: $EDITOR_TOKEN" "http://127.0.0.1:$BIND_PORT/health" >/tmp/editor-health.json 2>/dev/null; then
cat /tmp/editor-health.json; echo
say "部署完成。接下来:"
echo " · 宿主机 nginx(1Panel 站点)加反代:location ^~ / { proxy_pass http://127.0.0.1:$BIND_PORT; ... }"
echo " · Cloudflare Worker 变量:EDITOR_API_BASE=https://<指向本机的域名> EDITOR_TOKEN=$EDITOR_TOKEN"
echo " · 推送远端:PUSH_REMOTES=$PUSH_REMOTES"
exit 0
fi
sleep 2
done
die "健康检查失败,看日志:cd $APP_DIR && docker compose logs --tail=80"