#!/usr/bin/env bash # ============================================================================= # push-wiki.sh — 把 docs/wiki/*.md 推送/更新到本仓库的 Gitea wiki # # 为什么需要它:本项目的百科源文件**版本化在仓库里**(docs/wiki/), # Gitea wiki 只是它的发布面。改文档 = 改仓库文件 + 跑本脚本,日志与回溯都在 git 里。 # # 行为: # · 幂等:页面不存在则 POST 新建,已存在则 PATCH 覆盖(标题 = 文件名去掉 .md) # · 凭据来自 `git credential fill`(与推代码同一套),脚本自身不存任何 token # · 凭据经临时 netrc 文件传给 curl(600 权限,退出即删),不进入 argv # # 用法: # bash scripts/push-wiki.sh # 推送全部页面 # bash scripts/push-wiki.sh 05-运维手册 # 只推指定页面(按文件名/片段匹配) # WIKI_REMOTE=origin bash scripts/push-wiki.sh # 指定 remote(默认 gitea,缺失则 origin) # ============================================================================= set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$ROOT" WIKI_DIR="$ROOT/docs/wiki" FILTER="${1:-}" # ---- 解析 remote / owner / repo ------------------------------------------- pick_remote() { for r in "${WIKI_REMOTE:-}" gitea origin; do [ -z "$r" ] && continue if git remote get-url "$r" >/dev/null 2>&1; then echo "$r"; return 0; fi done echo "无法找到可用的 git remote(试过: ${WIKI_REMOTE:-<空>} gitea origin)" >&2 return 1 } REMOTE="$(pick_remote)" URL="$(git remote get-url "$REMOTE")" case "$URL" in https://*) ;; *) echo "本脚本只支持 https remote(当前: $URL)" >&2; exit 1 ;; esac HOST="$(printf '%s' "$URL" | sed -E 's#^https://([^/]+)/.*#\1#')" SLUG="$(printf '%s' "$URL" | sed -E 's#^https://[^/]+/##; s#\.git$##')" OWNER="${SLUG%%/*}" REPO="${SLUG#*/}" API="https://$HOST/api/v1/repos/$OWNER/$REPO/wiki" echo "wiki 目标 : $OWNER/$REPO @ $HOST (remote: $REMOTE)" echo "源目录 : docs/wiki" echo # ---- 凭据 ----------------------------------------------------------------- CRED="$(printf 'protocol=https\nhost=%s\n\n' "$HOST" | git credential fill 2>/dev/null || true)" USER="$(printf '%s\n' "$CRED" | sed -n 's/^username=//p')" PASS="$(printf '%s\n' "$CRED" | sed -n 's/^password=//p')" if [ -z "$PASS" ]; then echo "未从 git credential helper 取到 $HOST 的凭据(先确保 git push 能成功)" >&2 exit 1 fi NETRC="$(mktemp)" chmod 600 "$NETRC" trap 'rm -f "$NETRC"' EXIT printf 'machine %s\nlogin %s\npassword %s\n' "$HOST" "$USER" "$PASS" > "$NETRC" json_body() { # $1 = file python3 - "$1" <<'PY' import base64, json, sys, pathlib p = pathlib.Path(sys.argv[1]) print(json.dumps({ "title": p.stem, "content_base64": base64.b64encode(p.read_text(encoding="utf-8").encode("utf-8")).decode("ascii"), }, ensure_ascii=False)) PY } api() { # $1 = method, $2 = path, $3 = body(optional) -> 输出 HTTP 码 local method="$1" path="$2" body="${3:-}" if [ -n "$body" ]; then curl -s --netrc-file "$NETRC" -o /tmp/push-wiki-resp.json -w '%{http_code}' \ -X "$method" -H 'Content-Type: application/json' --data-binary "$body" "$API$path" else curl -s --netrc-file "$NETRC" -o /tmp/push-wiki-resp.json -w '%{http_code}' -X "$method" "$API$path" fi } # ---- 逐页推送 ------------------------------------------------------------- shopt -s nullglob files=("$WIKI_DIR"/*.md) [ ${#files[@]} -gt 0 ] || { echo "docs/wiki 下没有 .md 文件" >&2; exit 1; } ok=0; fail=0 for f in "${files[@]}"; do title="$(basename "$f" .md)" if [ -n "$FILTER" ] && [[ "$title" != *"$FILTER"* ]]; then continue; fi body="$(json_body "$f")" code="$(api POST /new "$body")" if [ "$code" = "201" ]; then echo " + $title (新建)"; ok=$((ok+1)); continue fi enc="$(python3 -c 'import sys,urllib.parse;print(urllib.parse.quote(sys.argv[1], safe=""))' "$title")" code="$(api PATCH "/page/$enc" "$body")" if [ "$code" = "200" ]; then echo " ~ $title (更新)"; ok=$((ok+1)) else echo " ! $title 失败(HTTP $code): $(head -c 200 /tmp/push-wiki-resp.json 2>/dev/null)" >&2 fail=$((fail+1)) fi done echo echo "完成:成功 $ok 页,失败 $fail 页" [ "$fail" -eq 0 ] || exit 1 echo "查看: https://$HOST/$OWNER/$REPO/wiki"