#!/usr/bin/env python3 """Halo DB 只读导出(Phase 2 T1 补充源):posts + snapshots → .migration/。 公共 API 只有摘要、head-content 404;正文真源在 postgres extensions 表: /registry/blog.halo.run/posts/{name} → spec.headSnapshot 指针 /registry/content.halo.run/snapshots/{uuid} → raw/content 全量 data 列是 bytea,经 base64 通道取出后 json 解析(psql 文本输出会搅转义)。 """ import base64 import json import subprocess import sys from pathlib import Path OUT = Path(".migration") def psql_b64(name: str) -> dict: sql = "SELECT encode(data,'base64') FROM extensions WHERE name = '%s';" % name raw = subprocess.run( ["docker", "exec", "postgres-db", "psql", "-U", "wait4x", "-d", "halo_db", "-t", "-A", "-c", sql], capture_output=True, text=True, check=True, ).stdout.strip() return json.loads(base64.b64decode(raw)) def names_like(pattern: str) -> list[str]: sql = "SELECT name FROM extensions WHERE name LIKE '%s';" % pattern raw = subprocess.run( ["docker", "exec", "postgres-db", "psql", "-U", "wait4x", "-d", "halo_db", "-t", "-A", "-c", sql], capture_output=True, text=True, check=True, ).stdout.split() return raw def main() -> int: posts = [psql_b64(n) for n in names_like("%/posts/%")] snaps = {n.rsplit("/", 1)[1]: psql_b64(n) for n in names_like("%/snapshots/%")} # 每篇 post 挂上 head snapshot 的 raw/content 全文 # 结构:snapshot.spec.{rawPatch,contentPatch} 直接是字符串(实测解剖) for p in posts: spec = p.get("spec", {}) # head 与 release 各挂一个 snapshot;实测存在"head 空、release 满"与 # 反例(mian-sha),取 content 更长者作为迁移源 cands = [snaps.get(k, {}).get("spec", {}) for k in (spec.get("releaseSnapshot"), spec.get("headSnapshot")) if k] snap = max(cands, key=lambda sp: len(sp.get("contentPatch", "") or ""), default={}) p["_snapshot"] = { "rawType": snap.get("rawType"), "raw": snap.get("rawPatch", ""), "content": snap.get("contentPatch", ""), "snapshotCount": len(cands), } (OUT / "posts_db.json").write_text(json.dumps(posts, ensure_ascii=False, indent=1)) ok = sum(1 for p in posts if p["_snapshot"]["content"]) print(f"db export: posts={len(posts)}(含正文 {ok});snapshots={len(snaps)}") return 0 if __name__ == "__main__": sys.exit(main())