Files
blog/deploy/editor-api/bootstrap.sh
T
zqlit b39dc29753 chore(远端): 移除 GitHub,改为「CNB 主仓 + Gitee 辅仓」
起因:GitHub 账号被平台标记,随后仓库被按 AUP 合规条款清空 ——
远端只剩一条孤立提交(无父提交、无内容):

    c21d5669  2026-10-06 20:08:32 +0800  zqlit
    chore: remove repository content (AUP compliance)

同时远端 main 的历史与本地**完全分叉**:两边只共享 2024-07-11 的 Initial commit,
之后每个提交 SHA 都不同(远端那份 989 提交、剥离了 .env/私钥/大二进制)。
所以 `git push gh main` 无法快进,也不可能再当备份用。

改动:

- `git remote remove gh`(改动前配置存 .workbuddy-backup/git-remotes.20261006-201354.txt)
- `pushall` 别名 → `git push origin main; git push gitee main`
- `scripts/setup-cnb-remotes.sh`:参数化第二个远端为 Gitee,
  顺带把「若残留 gh remote 就清掉」做进脚本;Gitee 凭据走 wincred
- `架构总览.md`:§2 发布旅程图、§5.2 远端表、§6 遗留待办全部改指向;
  新增 §5.2 的 GitHub 退役记录(含那条孤立提交的原文)
  与 §5.3「Gitee 辅仓的两条硬约束」实测核算
- `CNB构建落地方案.md`、`README.md`:远端与 pushall 说明同步
- **发布链路一起去 GitHub 化**(这几处原先都写死了 gh):
    · `deploy/editor-api/bootstrap.sh`  GH_URL/GH_SSH_KEY → GITEE_URL/GITEE_SSH_KEY,
      remote `gh` → `gitee`,known_hosts/私钥文件名跟着改;
      保留原设计:**没给私钥就自动降级成只推 origin**,不会让每次发布都报错
    · `docker-compose.editor.yml`   PUSH_REMOTES → origin,gitee
    · `editor-api/server.mjs`       默认值 → 'origin,gitee'
    · `editor-api/README.md`        变量表同步

★ 尚未解决 / 需要决策的两条 Gitee 硬约束(详见 架构总览.md §5.3):

1. **单文件 ≤ 50MB,而 `bin/linux/hugo` 是 83.1MB**
   —— 不管怎么瘦身历史,只要它还跟踪在 HEAD 里,Gitee 一律拒收。
2. 单仓库 ≤ 500MB,而 `.git` 是 620MB
   —— 移出那个 83MB 后 HEAD ≈ 374MB,才有余量。

另:线上 editor-api 容器的 .env 仍是 `PUSH_REMOTES=origin,gh`,
且仓库里还有一个 `gh` remote。因为推送逻辑对辅仓失败只警告、不阻断发布
(`git.mjs`:主仓成即算成),所以**线上发布没有坏**;
但要真正切到 Gitee,需要等 Gitee 仓库与私钥就位后再部署一次。
2026-10-06 20:18:29 +08:00

188 lines
7.9 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 转发时会带上,后端用它鉴权;浏览器永远看不到)
# ADMIN_PASS 后台登录页的密码(账号固定 admin,默认 3070210zz)。
# 后台登录走 editor-api 签发的 Cookie 会话,不再用 nginx basic auth。
#
# 可选环境变量(都有默认值):
# CNB_URL 默认 https://cnb.cool/zqlit/blog.git
# GITEE_URL 默认 git@gitee.com:zqlit/blog.git(配了 GITEE_SSH_KEY 才会推它)
# GITEE_SSH_KEY 可选:Gitee 部署私钥的**内容**。给了才会把 gitee 加进推送远端,
# 否则 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,gitee
# GIT_AUTHOR_NAME / GIT_AUTHOR_EMAIL / BLOG_BASE
#
# ★ 2026-10-06:原先的 GitHub(GH_URL / GH_SSH_KEY / remote `gh`)已随账号被标记
# 而退役,备份远端改为 Gitee。旧的 GH_* 变量不再被读取。
# =====================================================================
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 里的一致)}"
: "${ADMIN_PASS:?必须设置 ADMIN_PASS(后台登录密码,账号固定 admin)}"
CNB_URL="${CNB_URL:-https://cnb.cool/zqlit/blog.git}"
GITEE_URL="${GITEE_URL:-git@gitee.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,gitee}"
# 没有 Gitee 私钥就只推主仓 —— 宁可少推一个备份,也不要让每次发布都报错
if [ -z "${GITEE_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
# gitee 辅仓远端:只有给了私钥才配(没给就只推 origin,见上面 PUSH_REMOTES 降级)
if [ -n "${GITEE_SSH_KEY:-}" ]; then
install -d -m 700 ~/.ssh
printf '%s\n' "$GITEE_SSH_KEY" > ~/.ssh/blog_editor_gitee
chmod 600 ~/.ssh/blog_editor_gitee
ssh-keyscan -t rsa,ed25519 gitee.com >> ~/.ssh/known_hosts 2>/dev/null || true
git -C "$BLOG_DIR" remote remove gitee 2>/dev/null || true
git -C "$BLOG_DIR" remote add gitee "$GITEE_URL"
git -C "$BLOG_DIR" config core.sshCommand "ssh -i ~/.ssh/blog_editor_gitee -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 'ADMIN_USER=%s\n' "${ADMIN_USER:-admin}"
printf 'ADMIN_PASS=%s\n' "$ADMIN_PASS"
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 并启动容器"
# ★ 仓库与回收站必须落在**同一个挂载点**里。
# 若容器内把它们挂成两个独立 bind mount,rename(2) 跨挂载点必返回 EXDEV
# ——哪怕宿主机上它们同在一块盘上。后端虽已能退化成「复制+删除」,但那是白搬一遍数据。
# 做法:挂载 BLOG_DIR 的**父目录**,让容器内路径 == 宿主机路径。
MOUNT_ROOT="$(dirname "$BLOG_DIR")"
if [ "${TRASH_DIR#"$MOUNT_ROOT"/}" != "$TRASH_DIR" ]; then
# 回收站就在仓库父目录下 → 一个挂载点搞定
BLOG_IN_CT="$BLOG_DIR"
TRASH_IN_CT="$TRASH_DIR"
VOLUMES=" - $MOUNT_ROOT:$MOUNT_ROOT"
else
# 回收站被指到别处 → 只能两条独立挂载(后端会自动复制+删除,只是慢一点)
BLOG_IN_CT=/blog
TRASH_IN_CT=/app/trash
VOLUMES=" - $BLOG_DIR:/blog
- $TRASH_DIR:/app/trash"
fi
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_IN_CT
TRASH_DIR: $TRASH_IN_CT
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:-}
ADMIN_USER: \${ADMIN_USER:-admin}
ADMIN_PASS: \${ADMIN_PASS:?}
MAX_UPLOAD_MB: "20"
GIT_PATHS: content,static
volumes:
$VOLUMES
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"