Files
cnbandCursor cf8eba082f 用 marknb 替换 Python markdown,并添加一键安装与升级脚本。
部署时通过 install.sh 自动编译 marknb;日常更新用 upgrade.sh 拉代码并重编译。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-05 11:48:18 +08:00

9.0 KiB
Raw Permalink Blame History

py-blog

像写一份课程作业那样,搭一个属于自己的博客。
Build your own blog the way you'd hand in a class project.

中文 · English


中文文档

为什么要再造一个博客

市面上的博客产品越来越像「企业级 CMS」:装一堆插件、配一套主题、改一坨 YAML, 最后才能写下第一行字。对一个刚入门的同学来说,这个过程不是在写博客, 是在向所有人宣告自己「不会用」。

但是新手也有写博客的需求:

  • 想把课程笔记整理出来;
  • 想把第一份小作品挂到自己的域名下;
  • 想拥有一个写得不漂亮、但完全属于自己的角落。

py-blog 就是为这种需求做的。

核心理念:像项目作业一样构建博客

  • 一个 blog.py 文件就是整个项目,没有 src/,没有十层目录。
  • 看得懂 Python 基本语法,就看得懂这个博客;想改首页样式,直接搜模板字符串。
  • 部署像交作业:装依赖、跑脚本、看终端输出 —— 没有 Webpack,没有 Node。

功能一览

  • 单文件 PythonFastAPI + SQLite + Jinja2一份脚本即一个站点
  • Markdown 写作:上传 .md 即可,保存时预渲染为 HTML 缓存,访问零开销。
  • 数学公式:内置 KaTeX,支持 $...$ / $$...$$ / \(...\) / \[...\]
  • 图片走图床:正文里用外链 URL,省去本地存储与备份烦恼。
  • 树形分组:按目录组织文章,同级按字典序,文章可按时间或手动顺序排列。
  • 单管理员后台:密码登录、CSRF、Session,够用且不繁琐。
  • 备案号挂载:BLOG_ICP_TEXT / BLOG_ICP_URL 环境变量直接生效,国内合规。
  • 反代友好:默认监听 127.0.0.1:8765,套 Nginx 即可上 HTTPS。
  • Markdown 引擎:marknb — 零堆分配 C 解析器,比 Python markdown 更快,支持 GFM 表格与 LaTeX 透传。

30 秒上手

git clone https://cnb.cool/whcself/py-blog.git
cd py-blog
bash scripts/install.sh
source venv/bin/activate
python blog.py

install.sh 会自动从 marknb 拉取源码、编译为 vendor/marknb/libmarknb.so,并创建 venv 安装 Python 依赖。

升级

已部署的站点拉取新版本后,一条命令完成代码、marknb 与 pip 依赖更新:

cd py-blog
bash scripts/upgrade.sh
source venv/bin/activate
python blog.py   # 重启服务

upgrade.sh 会依次:git pull py-blog → 重编译 marknb → pip install -U 依赖。若尚未安装过,会自动回退执行 install.sh

脚本一览

脚本 用途
scripts/install.sh 首次部署:系统依赖 + marknb 编译 + venv
scripts/upgrade.sh 日常升级:拉代码 + 重编译 marknb + 更新 pip

打开 http://127.0.0.1:8765,登录入口在 /admin/login。 若没设置 BLOG_ADMIN_PASSWORD首次启动会在终端打印一次随机密码,请立刻收好。

环境变量

变量 默认 说明
BLOG_HOST 127.0.0.1 监听地址,建议保持本机由 Nginx 反代
BLOG_PORT 8765 监听端口
BLOG_DATA_DIR data SQLite 与运行数据目录
BLOG_SITE_TITLE 站点标题
BLOG_ADMIN_PASSWORD 管理员密码;未设置则首次启动随机生成并打印
BLOG_SECRET_KEY Session 签名密钥;未设置则自动生成并持久化
BLOG_ICP_TEXT 页脚备案文案,例如 京ICP备xxxxxx号-1
BLOG_ICP_URL 备案查询链接,例如 https://beian.miit.gov.cn/
MARKNB_LIB vendor/marknb/libmarknb.so marknb 动态库路径(可选)

Nginx 反代示例

server {
    listen 443 ssl http2;
    server_name your.domain.com;

    # ssl_certificate ...;
    # ssl_certificate_key ...;

    large_client_header_buffers 8 16k;

    location / {
        proxy_pass http://127.0.0.1:8765;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

若使用 EdgeOne / WAF,请为 /admin/* 添加放行规则,避免登录 POST 被拦截。

不打算做的事

  • 不内置富文本编辑器:写 Markdown 用你顺手的本地编辑器即可。
  • 不内置评论系统:需要时挂 Giscus / Disqus / Waline。
  • 不做主题市场:想换样式直接改模板字符串,这才像做作业

协议

MIT。随便用,写得不好别骂得太狠。


English

Why another blog

Most blog platforms today feel like enterprise CMSs: plugins, themes, YAML configs and a build pipeline before you can write a single sentence. For a beginner, that whole process isn't writing a blog — it's announcing to the world that you can't use one.

But beginners also deserve a blog:

  • to publish their class notes;
  • to put their very first project under their own domain;
  • to own a small, imperfect corner of the internet.

py-blog is built for that.

Core idea: build a blog like a class project

  • One file: blog.py is the whole project. No src/, no ten-level tree.
  • If you can read basic Python, you can read this blog. Want to tweak the homepage? Search the template strings inline.
  • Deploying feels like submitting homework: install deps, run the script, read the terminal. No Webpack, no Node, no incantations.

Features

  • Single-file Python: FastAPI + SQLite + Jinja2 — one script, one site.
  • Markdown-first: upload .md files; HTML is pre-rendered on save.
  • Math: KaTeX out of the box ($...$, $$...$$, \(...\), \[...\]).
  • Images via external CDN (image hosts) — no local upload/backup hassle.
  • Tree-structured categories; siblings sorted A→Z; articles sorted by time or by a manual display_order.
  • Single-admin panel: password login, CSRF, sessions. Enough, not more.
  • Built-in ICP footer (BLOG_ICP_TEXT / BLOG_ICP_URL) for hosting in China.
  • Reverse-proxy friendly: binds to 127.0.0.1:8765 by default; put Nginx in front for HTTPS.
  • Markdown engine: marknb — zero-malloc C parser, faster than Python markdown, with GFM tables and LaTeX passthrough.

Quick start

git clone https://cnb.cool/whcself/py-blog.git
cd py-blog
bash scripts/install.sh
source venv/bin/activate
python blog.py

install.sh clones marknb, builds vendor/marknb/libmarknb.so, and creates a venv with Python deps.

Upgrade

On an existing deployment, one command updates code, marknb, and pip packages:

cd py-blog
bash scripts/upgrade.sh
source venv/bin/activate
python blog.py   # restart the service

upgrade.sh runs git pull → rebuild marknb → pip install -U. Falls back to install.sh if no venv exists yet.

Scripts

Script Purpose
scripts/install.sh First deploy: system deps + marknb build + venv
scripts/upgrade.sh Routine upgrade: pull + rebuild marknb + pip update

Open http://127.0.0.1:8765. Admin login lives at /admin/login. If BLOG_ADMIN_PASSWORD is unset, a random password is printed once to the terminal on first boot — copy it immediately.

Environment variables

Variable Default Description
BLOG_HOST 127.0.0.1 Bind address; keep local and front with Nginx
BLOG_PORT 8765 Bind port
BLOG_DATA_DIR data Data directory (SQLite, etc.)
BLOG_SITE_TITLE Site title
BLOG_ADMIN_PASSWORD Admin password; auto-generated on first boot if unset
BLOG_SECRET_KEY Session signing key; auto-generated and persisted if unset
BLOG_ICP_TEXT Footer ICP text (China hosting)
BLOG_ICP_URL Footer ICP link
MARKNB_LIB vendor/marknb/libmarknb.so Path to marknb shared library (optional)

Nginx reverse proxy

server {
    listen 443 ssl http2;
    server_name your.domain.com;

    # ssl_certificate ...;
    # ssl_certificate_key ...;

    large_client_header_buffers 8 16k;

    location / {
        proxy_pass http://127.0.0.1:8765;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

If you sit behind EdgeOne / a WAF, whitelist /admin/* so the login POST isn't silently dropped.

Non-goals

  • No bundled rich-text editor — use your favourite Markdown editor locally.
  • No bundled comment system — drop in Giscus / Disqus / Waline if needed.
  • No theme marketplace — edit the template strings; that's the whole point.

License

MIT. Use it freely; be gentle when judging the code.