今天想做的事情其实很简单:把博客真正部署出去,让它不只是本地能跑,而是能在服务器上稳定更新、像一个正经网站那样对外可见。
真做起来才发现,这种“最后一步”往往最不简单。白天写页面和内容的时候,问题大多是视觉和逻辑上的;到了晚上开始上线,面对的就变成了权限、SSH、目录归属、GitHub Secrets,还有国内站点绕不开的备案信息展示。
这些东西单独看都不复杂,但凑在一起,就会把一个本来像是“点一下发布”的动作,变成一次完整的工程收尾。
从能构建,到能自动部署
一开始我先把博客的部署工作流补了出来:在推送到主分支之后,由 GitHub Actions 自动安装依赖、构建静态文件,然后通过 SSH 和 SCP 上传到服务器。
这一步的核心其实不是写 workflow 本身,而是想清楚两件事:
- GitHub 到底该拿什么身份登录服务器
- 这个身份应该拥有哪些权限
看上去最省事的做法当然是直接用 root。服务器现在确实也只有 root 用户,很多人第一次部署都会这么干,因为它“肯定能跑”。
但 root 能跑,不代表它是对的。
一旦把 root 私钥塞进 GitHub Secrets,后面的部署链路就不是“能不能成功”的问题了,而是“万一泄露了会怎样”的问题。部署博客这种事,本质上只需要把构建产物同步到某个目录,不需要整个系统的完全控制权。
所以这里真正该做的,不是想办法让 root 更方便,而是把部署这件事从 root 身上拆下来。
给部署单独建一个用户
服务器当前情况很直接:只有 root,网站运行侧关联的是 1000:1000,也就是 admin
这其实把问题说得很清楚了:网站运行和部署写入并不是一回事。
我后面选择的方案也很朴素:
- 创建一个专门用于发布博客的用户,比如 blogdeploy
- 把它加入 admin 组
- 让部署目录的属主变成 blogdeploy,属组保持 admin
- 给目录加上组可写和 setgid,保证后续上传的文件继续继承同一个组
这样一来,部署用户只负责“写进去”,站点运行侧继续负责“读出来”,职责被明确拆开,权限边界也清楚得多。
大致对应的命令就是这些:
adduser --disabled-password --gecos "" blogdeployusermod -aG admin blogdeploy
mkdir -p /home/blogdeploy/.sshchmod 700 /home/blogdeploy/.sshtouch /home/blogdeploy/.ssh/authorized_keyschmod 600 /home/blogdeploy/.ssh/authorized_keyschown -R blogdeploy:blogdeploy /home/blogdeploy/.ssh
chown -R blogdeploy:admin /opt/1panel/www/sites/blog.yangyus8.top/indexfind /opt/1panel/www/sites/blog.yangyus8.top/index -type d -exec chmod 2775 {} +find /opt/1panel/www/sites/blog.yangyus8.top/index -type f -exec chmod 664 {} +这里面我最在意的一点,不是“成功上传”,而是上传之后权限不要慢慢漂移。
如果没有 setgid,今天上传进去的文件可能没问题,过几次部署以后,目录和文件的组归属就可能开始混乱。对静态站点来说,这种问题不会立刻把站点打挂,但会在之后某次更新里突然冒出来,然后让人怀疑半天到底是 Nginx、面板还是脚本的问题。
工程里最烦的,往往不是立刻报错,而是那种埋着不炸、等以后再炸的配置。
Secrets 这件事,说白了就是身份问题
把部署用户拆出来之后,GitHub Actions 里需要的 secrets 也就很明确了。
其实只有两个:
- DEPLOY_USER
- DEPLOY_SSH_KEY
前者填 blogdeploy 这样的登录用户名,后者填对应私钥的完整内容。
这看起来没什么技术含量,但它逼着我重新把自动部署理解成一件很具体的事:
GitHub 不是“帮我部署”,它只是“替某个身份去执行部署”。如果这个身份定义得含糊,后面的安全边界就会一起含糊;如果这个身份从一开始就是最小权限,那整个链路就会稳很多。
很多时候,工程质量并不体现在那些更复杂的部分,而体现在你有没有认真对待这些看起来很基础的约束。
国内站点绕不开的一步:备案信息
部署跑通之后,另一个必须补上的事情,就是备案信息展示。
ICP备案和公安备案都不是什么“可有可无的小字”,它们本质上属于站点级信息,最合理的位置就是全站页脚。放在某个单独页面里当然也能写,但用户不点进去就看不到,这不太符合它应该承担的作用。
我一开始也想过,直接把备案号硬编码进页脚算了,反正只有两行字。
但最后还是把它做成了配置项。
原因也不复杂:
- 备案号本身就是站点配置,不是组件逻辑
- 以后如果域名、备案主体或图标路径变化,不应该再去改模板
- 能配置的东西尽量配置,后续维护成本会低很多
所以最后做的事情是:
- 给站点配置增加 filings 字段
- 支持分别配置 ICP 备案和公安备案
- 公安备案图标走 public 目录静态资源
- 页脚组件只负责按配置渲染
配置大致长这样:
filings: { icp: { enable: true, text: "辽ICP备2024030730号-1", url: "https://beian.miit.gov.cn/", }, police: { enable: true, text: "川公网安备51100002000181号", url: "https://beian.mps.gov.cn/#/query/webSearch?code=51100002000181", icon: "/beian-police.svg", },}这种改法的好处是,备案信息终于回到了它应该待的地方:配置层,而不是模板里的散落字符串。
对我来说,这类调整比单纯“把功能做出来”更重要,因为它决定了这个站点以后是越改越乱,还是越改越顺。
这不是大工程,但它很像真正的工程
回头看这一晚上,做的事情并不是什么宏大的系统设计:
- 写了部署工作流
- 确定了 GitHub Secrets 应该怎么填
- 给服务器建立了专门的部署用户
- 理顺了目录权限和组继承
- 把备案信息接进了页脚,而且做成了可配置
每一项单拎出来都不算“技术挑战”,甚至有些看起来相当琐碎。
但也正因为如此,它们很像真实开发里的大多数工作:不是为了炫技,不是为了堆复杂度,而是为了把一个东西从“勉强能用”推进到“可以长期维持”。
我越来越觉得,很多项目的成熟,不是从你写出第一个页面开始的,而是从你愿意认真处理这些边角开始的。
权限怎么收、发布怎么做、信息放哪、配置怎么抽、以后怎么维护。这些东西不会直接让页面更炫,也不会让访问者立刻注意到,但它们决定了这个站点能不能被安稳地继续维护下去。
第一篇,就先记下这些收尾工作
如果这是这个博客的第一篇文章,那它拿来记录这些事情,我觉得还挺合适。
因为博客本身并不是某个抽象表达空间,它首先是一个真的会被构建、被部署、被访问、被维护的网站。它有路径、有权限、有配置、有约束,也有很多只有亲手把它上线的人才会在意的细节。
今晚做完这些之后,这个站点对我来说才第一次真正像“已经开始运行”了。
后面当然还会继续折腾样式、内容、结构和功能,但至少从这一刻开始,它已经不只是本地目录里的一堆文件,而是一个能够稳定发布、持续更新,并且开始留下记录的地方。
这篇就当作开工记录。
也是上线前夜的存档。