从空白服务器到自动部署:「syjzt的博客」搭建手记
“让网页能打开”和“做一个愿意一直写下去的博客”是两码事。这篇记下用 Astro、Caddy 和 GitHub Actions 跑通第一版发布链路的过程,以及页面从粒子动画改到技术档案风的几次迭代。

起初只是想把手头一台吃灰的云服务器用起来。里面反正没什么数据,干脆重装系统,自己搭个博客。
真正动手才发现,“让网页能打开”跟“做出一个自己愿意一直写下去的博客”,完全是两码事。
这篇笔记主要记录从空白服务器到自动部署的全过程。期间的代码和配置借了 AI 的力,我则顺着终端输出、浏览器表现和部署日志一步步核对结果。
文中的服务器地址和域名均为占位符,部署密钥等敏感信息已隐去。前半部分保留了搭站时的踩坑记录,后半部分的外观介绍则更新成了博客现在的样子。
先想清楚:我到底想做什么
最开始考虑过 WordPress,写文章、换主题都省心。但我更想自己把控排版和交互:正文读起来要清爽,首页得有一点个人特色,鼠标和键盘操作也要有及时的反馈。
平时习惯用 Markdown 写东西,又想自己动手改界面,挑了一圈最后定了 Astro。
第一版砍掉了评论、登录、点赞和站内搜索。先把“写文章、看文章、发文章”这条主线跑通,免得第一篇字还没写,先背上一堆用不上的服务。
最终定下来的技术栈分工如下:
| 部分 | 这次的选择 |
|---|---|
| 内容与页面 | Astro、Markdown / MDX |
| 粒子动画 | Canvas 绘制,GSAP 控制动画 |
| 本地开发 | Windows、Node.js、Git |
| 构建发布 | GitHub Actions |
| 服务器 | Ubuntu、Caddy |
| 域名解析 | DNSPod |
项目里虽然装了 React 集成,但目前首页的字符场是直接用 Canvas 2D 画的,并没有做成 React 组件。编译打包交给 GitHub Actions,服务器只管托管生成的静态文件,省去了在云端配整套 Node 环境的麻烦。
第一关:重装系统后,SSH 连不上
刚重装完系统,还没碰到代码,先撞上了 SSH 警告:
WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!
服务器重装后生成了新的主机密钥,而本地电脑还存着旧指纹。遇到这个提示先别急着强行跳过,去云控制台核对一下实例状态和新密钥指纹,确认连的是自己那台机器后,再清理本地的旧记录重连。直接关掉主机密钥检查虽然省事,但隐患太大。
清掉旧记录再连,又弹出了第二个报错:
Permission denied, please try again.
我习惯性地用 root 去登,试了好几次才反应过来,这次选用的 Ubuntu 云镜像要用 ubuntu 账号:
ssh ubuntu@YOUR_SERVER_IP
不同云镜像的默认用户名和认证方式可能不同,要以镜像说明为准。账号名填错,反复输密码也没用。
第二关:先让一个最简单的网页跑起来
博客代码还没开始写时,我先完成系统更新、基础工具和防火墙配置,再安装 Caddy。
云控制台防火墙和系统里的 UFW 是分开的配置。这次保留 SSH 管理入口,并开放网站需要的 HTTP、HTTPS 端口,没有为了省事放开所有端口。
站点目录是:
/var/www/blog
最开始里面只有一张写着“服务器部署成功”的测试页面。先验证本机访问,再验证域名访问,就能把网络、服务和前端代码的问题分开。
在域名解析到服务器之后,Caddy 使用了类似这样的配置:
example.com, www.example.com {
root * /var/www/blog
file_server
}
这里的域名是示例,不要直接替换现有配置照抄。Caddy 的自动 HTTPS 会在满足条件时申请和续期证书,具体前提应查看官方文档。
第三关:配置跑通了,但证书还没签下来
启用域名配置后,我立即打开 HTTPS,浏览器提示:
ERR_SSL_PROTOCOL_ERROR
当时第一段日志只显示正在申请证书。稍后查看日志,才出现:
certificate obtained successfully
这次确实是证书申请还没结束,不需要删除证书目录或不断重写配置。但这也不意味着所有 HTTPS 错误都只需要等一会儿,还是要根据日志判断。
一个可以复用的只读检查命令是:
sudo journalctl -u caddy --since "10 minutes ago" --no-pager
另外,这次排查曾使用 curl -k。它会跳过证书校验,所以返回 200 只能说明在跳过校验的条件下收到了网页,不能单独证明证书受信任。最终验收应使用浏览器正常访问,或不带 -k 的请求。
本地初始化:从 Astro 模板改起
本地项目放在 D:\Blog,从 Astro 的博客模板开始,安装依赖并初始化 Git。
模板先提供文章结构,我再逐渐替换导航、首页和正文样式。英文示例文章也需要主动清理,模板不会自动变成自己的内容。
这个项目的常用命令很简单:
cd D:\Blog
npm run dev
开发时访问终端输出的本地地址。提交之前再运行:
npm run build
本项目构建后的静态文件位于 dist。这次没有在服务器上安装整套前端开发环境,而是让 GitHub Actions 构建,再上传结果。
第四关:自动部署卡在了私钥读取
发布链路是:
本地修改文章
→ Git 提交并推送
→ GitHub Actions 安装依赖、构建
→ SSH + rsync 上传 dist
→ Caddy 提供静态文件
第一次部署并没有成功。查看具体步骤后发现,依赖安装和构建已经通过,真正失败的是发布步骤:
Load key ".../deploy_key": error in libcrypto
SSH 连私钥都没读出来,先查密钥文件,而不是服务器目录权限。
这次的处理是把本地私钥按原始字节编码成 Base64,放进 GitHub Actions Secret DEPLOY_KEY_B64,在运行器中解码回文件。修改之后,下一次工作流运行成功了。
需要记住两点:
- Base64 只是编码,不是加密;编码后的字符串仍然等同于私钥,不能公开。
- 如果原始密钥已经损坏、选错文件或需要密码,换成 Base64 也不会自动修好这些问题。
当前工作流还使用了 SERVER_HOST 和 SERVER_USER 两个 Secret。它们和部署密钥都应该在仓库设置中管理,而不是写在文章里。
外观演进:从粒子文字到现在的技术档案风
最早试过深色背景和旋转圆环,后来又做了 SYJZT 粒子文字,鼠标靠近时散开,离开后重新聚合。这些效果挺适合拿来做交互实验,不过随着文章越来越多,我也开始调整首页的重点,让标题、最近更新和文章入口更容易找到。
博客最初取名为「游戏存档」,后来改成了「syjzt的博客」。名字换了,副标题一直留着:
把探索写成代码,把日常存成进度。
现在页面以浅色底、黑色文字为主,用蓝色强调链接和交互。大标题、细分隔线和等宽的小字标签,把文章列表排成一份可以慢慢翻阅的技术档案;同时也保留了暗色模式。
首页首屏换成了会缓慢变化的 ASCII 字符场,中间是一枚字符组成的菱形存档点。鼠标经过会扰动它,点击会产生扩散效果。旁边的终端式列表直接列出最近三篇文章,往下就是带封面和摘要的最新记录,再接简洁的文章索引。
动画保留在进入页面、滚动和操作反馈里,链接和按钮有轻微过渡。字符场在离屏或页面隐藏时暂停,也照顾了系统的减少动态效果设置。想找旧文章,可以用 Ctrl + K 或 / 打开搜索。
阅读页则把空间留给正文:中文行距、侧边目录、代码复制、图片预览,以及底部的上一篇和下一篇入口。窄屏下重新安排布局,让长标题和代码块也能读下去。
首页可以活泼,读长文的地方还是安静些好。
跑通之后,还有哪些待办
最初写下这篇记录时,自动部署已经跑通,备案还在处理中。现在文章已经逐渐积累起来,外观也换了几轮,后续更想把精力放在阅读体验和发布稳定性上。
另外还有一些后续事项:
- 继续整理旧内容和封面,保持文章列表的呈现一致。
- 控制封面、字体和动图的加载体积,减少打开页面时的等待。
- 将部署账号权限限制到确实需要的范围。
- 用可信渠道核验并固定 SSH 主机密钥,而不是把运行时扫描结果直接当作信任依据。
- 把目前直接
rsync覆盖目录的做法改成“上传新目录 + 软链接切换”(或蓝绿目录),避免发布中途失败导致新旧文件混杂,同时方便随时回滚。
架子搭好了,自动发布也跑顺了,这些加固细节就留在后面的更新里慢慢填。
第一份存档
回头看这次搭站,最受用的习惯还是“把问题拆小”:SSH 能不能登、Caddy 有没有起、证书签没签下来、本地构建过没过、文件传没传上去。
遇到报错先看它卡在哪一环,顺着日志单点排查,远比推倒重装管用得多。
第一份存档就记到这里,接下来该多写几篇正经笔记了。
参考文档
- Astro 官方文档:内容集合与 Markdown 渲染。
- Caddy 官方文档:Automatic HTTPS。
- GitHub 官方文档:Using secrets in GitHub Actions。