从零到一:个人博客自动化部署踩坑全记录

写在前面

博客站点已经搭好,接下来要把站点部署到云服务器上。服务器是腾讯云,管理面板用的是 1Panel。

思路很简单:在 1Panel 里创建一个静态网站,然后用 GitHub Actions 自动构建,再通过 rsync 把产物同步到服务器。之后每次推送到 main,网站就会自动更新。整条链路分四步:创建静态网站、配置 SSH 公钥认证、配置 GitHub 仓库与 Secrets、编写工作流。

在 1Panel 创建静态网站

按 1Panel 的指引新建一个静态网站即可。创建完成后,到网站设置里记下站点根目录,后面配置 DEPLOY_PATH 会用到。路径一般类似:

text 复制代码
/opt/1panel/www/sites/你的域名/index

确认网站能访问到这个目录下的文件,再往下做部署。路径配错的话,后面 rsync 带 --delete 会把错误目录里的文件清掉,务必核对。

配置 SSH 公钥认证

生成密钥

先在本地生成一把专门给 GitHub Actions 用的密钥,不要复用日常登录的那把:

bash 复制代码
# 生成 RSA 格式密钥(兼容性最好,避免格式报错)
ssh-keygen -t rsa -b 4096 -C "github-actions-deploy" -f ~/.ssh/id_rsa_github_actions

一路回车,不要设密码。生成的文件:

  • ~/.ssh/id_rsa_github_actions → 私钥(给 GitHub Actions,绝不能泄露)
  • ~/.ssh/id_rsa_github_actions.pub → 公钥(放到服务器)

为什么选 RSA 而不是 ED25519?

burnett01/rsync-deployments 对 OpenSSH 新格式(-----BEGIN OPENSSH PRIVATE KEY-----)兼容不好,常见报错是 error in libcrypto 或 invalid format,SSH 直接失败。

ssh-keygen -t rsa 生成的是传统 PEM 格式(-----BEGIN RSA PRIVATE KEY-----),这个 Action 吃得比较稳。

在服务器上添加公钥

bash 复制代码
# 切换到部署用户(我用的是 root,也可以用普通用户)
sudo su -

# 创建 .ssh 目录
mkdir -p /root/.ssh
chmod 700 /root/.ssh

# 添加公钥
echo "ssh-rsa AAAAB3... github-actions-deploy" >> /root/.ssh/authorized_keys
chmod 600 /root/.ssh/authorized_keys

用 root 是因为 1Panel 默认以 root 跑,静态网站目录的属主也是 root。如果改用普通用户,把公钥写进对应用户的 ~/.ssh/authorized_keys,并给网站目录授权。root 权限过大,能不用就不用。

验证免密登录

在本地执行:

bash 复制代码
ssh -i ~/.ssh/id_rsa_github_actions root@你的服务器IP

能直接进去、不弹密码,说明配置成功。

配置 GitHub 仓库

仓库结构

GitHub 仓库只放源代码(Markdown、主题、配置),不放构建后的 HTML。构建在 CI 里完成,再把 dist/ 同步到服务器。

text 复制代码
my-blog/
├── src/                 # 源代码
├── dist/                # 构建产物(.gitignore 忽略)
├── package.json
├── astro.config.mjs     # 框架配置
└── .github/
    └── workflows/
        └── deploy.yml   # GitHub Actions 工作流

Astro 的构建产物是 dist/,不是 public/。public/ 是源码里的静态资源,要提交进仓库。.gitignore 只忽略构建产物和依赖:

text 复制代码
dist/
node_modules/

配置 GitHub Secrets

进入仓库 → Settings → Secrets and variables → Actions,添加以下密钥。名称必须和工作流里引用的完全一致:

Secret 名称 示例值 说明
SSH_HOST 123.456.78.90 服务器 IP 或域名
SSH_USER root SSH 登录用户名
DEPLOY_PATH /opt/1panel/www/sites/blog.example.com/index 网站根目录(绝对路径)
SSH_PRIVATE_KEY -----BEGIN RSA PRIVATE KEY-----... 本地生成的私钥完整内容
SSH_PORT 22 可选,默认 22

私钥必须是完整的 PEM,头尾和中间换行都要保留:

text 复制代码
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA...
...(多行)...
-----END RSA PRIVATE KEY-----

复制时注意:不要漏掉 BEGIN / END,不要在首尾加空格,也不要多出空行。换行本身是私钥格式的一部分,不能压成一行。

编写 GitHub Actions 工作流

在 .github/workflows/deploy.yml 中写入:

yaml 复制代码
name: Deploy

on:
  push:
    branches: [main]
  workflow_dispatch:

concurrency:
  group: deploy-production
  cancel-in-progress: false

permissions:
  contents: read

# Required repository secrets:
#   SSH_HOST         - server hostname or IP
#   SSH_USER         - SSH username
#   SSH_PRIVATE_KEY  - private key of the deploy user
#   DEPLOY_PATH      - remote directory, e.g. /var/www/blog/
# Optional:
#   SSH_PORT         - SSH port, defaults to 22

jobs:
  deploy:
    runs-on: ubuntu-latest
    name: Build and rsync to server
    steps:
      - name: Checkout
        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

      - name: Setup pnpm
        uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0
        with:
          run_install: false

      - name: Setup Node.js
        uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
        with:
          node-version: 22
          cache: pnpm

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Build
        run: pnpm build

      - name: Rsync dist to server
        uses: burnett01/rsync-deployments@4d419d1dc46d4a8a2b6ceab2f951f0f93b2da8f5 # v9.0.0
        with:
          switches: -avzr --delete
          path: dist/
          remote_path: ${{ secrets.DEPLOY_PATH }}
          remote_host: ${{ secrets.SSH_HOST }}
          remote_port: ${{ secrets.SSH_PORT || 22 }}
          remote_user: ${{ secrets.SSH_USER }}
          remote_key: ${{ secrets.SSH_PRIVATE_KEY }}

推送到 main,或在 Actions 页面手动触发 workflow_dispatch。跑通后打开网站确认页面已更新。

rsync 的 --delete 会删掉远端目录里、本地 dist/ 没有的文件。DEPLOY_PATH 一定要指向网站根目录,不要指到上一级。

参考资料

相关推荐
miss14 分钟前
AI 改完不敢上线?OpenSpec 规约驱动开发实战
前端·代码规范·前端工程化
AiMan1 小时前
腾讯ima还是不可以导出左侧所有对话,只能单条导出,方法不对吗
前端
去伪存真1 小时前
并发量一高就装死?Agent 全链路“防猝死”手册
前端·agent
Moment1 小时前
AI 编程时代,聊聊我的 Mac mini 双屏桌面
前端·后端
AiMan1 小时前
先确认能不能一次把Microsofat Copilot所有内容打包一个文件导出
前端
总要学点什么1 小时前
第97篇:三维数据加载-城市建筑白模自定义着色器——让一条光带沿高度扫上去,快慢却由帧率说了算
前端·gis·webgl·cesium·着色器·空间分析·白模
上海懒图科技2 小时前
工厂厂区电子地图定位导航:厂区人车精准定位,安全合规双保障
前端·arcgis·vue
西安小哥2 小时前
端侧跑大模型,真正的门槛不在参数量:一次 596 秒到 10 秒的压缩路径复盘
前端·人工智能
陈随易2 小时前
Cloudflare 出钱,Vite 团队挑人,44 个维护者分 30 万美元
前端·后端·程序员
恋猫de小郭3 小时前
Flutter THLC 的 WebView 滚动终于从 70 FPS 提升到 120 FPS
android·前端·flutter