部署时通过 install.sh 自动编译 marknb;日常更新用 upgrade.sh 拉代码并重编译。 Co-authored-by: Cursor <cursoragent@cursor.com>
9.0 KiB
py-blog
像写一份课程作业那样,搭一个属于自己的博客。
Build your own blog the way you'd hand in a class project.
中文文档
为什么要再造一个博客
市面上的博客产品越来越像「企业级 CMS」:装一堆插件、配一套主题、改一坨 YAML, 最后才能写下第一行字。对一个刚入门的同学来说,这个过程不是在写博客, 是在向所有人宣告自己「不会用」。
但是新手也有写博客的需求:
- 想把课程笔记整理出来;
- 想把第一份小作品挂到自己的域名下;
- 想拥有一个写得不漂亮、但完全属于自己的角落。
py-blog 就是为这种需求做的。
核心理念:像项目作业一样构建博客
- 一个
blog.py文件就是整个项目,没有src/,没有十层目录。 - 看得懂 Python 基本语法,就看得懂这个博客;想改首页样式,直接搜模板字符串。
- 部署像交作业:装依赖、跑脚本、看终端输出 —— 没有 Webpack,没有 Node。
功能一览
- 单文件 Python:FastAPI + 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.pyis the whole project. Nosrc/, 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
.mdfiles; 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:8765by 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.