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

261 lines
9.0 KiB
Markdown
Raw Permalink 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.
<h1 align="center">py-blog</h1>
<p align="center">
<em>像写一份课程作业那样,搭一个属于自己的博客。</em><br/>
<em>Build your own blog the way you'd hand in a class project.</em>
</p>
<p align="center">
<a href="#中文文档">中文</a> · <a href="#english">English</a>
</p>
---
## 中文文档
### 为什么要再造一个博客
市面上的博客产品越来越像「企业级 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](https://cnb.cool/whcself/marknb) — 零堆分配 C 解析器,比 Python `markdown` 更快,支持 GFM 表格与 LaTeX 透传。
### 30 秒上手
```bash
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](https://cnb.cool/whcself/marknb) 拉取源码、编译为 `vendor/marknb/libmarknb.so`,并创建 `venv` 安装 Python 依赖。
### 升级
已部署的站点拉取新版本后,一条命令完成代码、marknb 与 pip 依赖更新:
```bash
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 反代示例
```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](https://cnb.cool/whcself/marknb) — zero-malloc C
parser, faster than Python `markdown`, with GFM tables and LaTeX passthrough.
### Quick start
```bash
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](https://cnb.cool/whcself/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:
```bash
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
```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;
}
}
```
> 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.