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。 ### 功能一览 - 单文件 Python:FastAPI + SQLite + Jinja2,**一份脚本即一个站点**。 - Markdown 写作:上传 `.md` 即可,**保存时预渲染**为 HTML 缓存,访问零开销。 - 数学公式:内置 KaTeX,支持 `$...$` / `$$...$$` / `\(...\)` / `\[...\]`。 - 图片走图床:正文里用外链 URL,省去本地存储与备份烦恼。 - 树形分组:按目录组织文章,同级按字典序,文章可按时间或手动顺序排列。 - 单管理员后台:密码登录、CSRF、Session,够用且不繁琐。 - 备案号挂载:`BLOG_ICP_TEXT` / `BLOG_ICP_URL` 环境变量直接生效,国内合规。 - 反代友好:默认监听 `127.0.0.1:8765`,套 Nginx 即可上 HTTPS。 ### 30 秒上手 ```bash git clone https://github.com/WHC2006/py-blog.git cd py-blog pip install -r requirements.txt python blog.py ``` 打开 ,登录入口在 `/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/` | ### 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. ### Quick start ```bash git clone https://github.com/WHC2006/py-blog.git cd py-blog pip install -r requirements.txt python blog.py ``` Open . 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 | ### 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.