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

写在前面

博客站点已经搭好,接下来要把站点部署到云服务器上。服务器是腾讯云,管理面板用的是 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 libcryptoinvalid 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 一定要指向网站根目录,不要指到上一级。

参考资料

相关推荐
淼澄研学1 小时前
Python调用通义千问API实现长尾搜题内容自动化生成实战
前端·react.js·架构
绿岛之北1 小时前
Electron 安全入门:为什么一个 XSS 可能变成 RCE?
前端·electron
舒灿1 小时前
DeepSeek Harness——Agent自我进化的实现途径?
前端·ai编程·deepseek
cindershade1 小时前
React Server Components 在真实项目中的边界:哪些组件该放在服务端
前端
OpenTiny社区1 小时前
GenUI SDK v1.3.0 开发者深度解读:当生成式 UI 开始"长出"工程化骨架
前端·ai编程
前端粉刷匠1 小时前
2025 年是 Agent 的,2026 年是 Harness 的——AI 编程 Harness 架构深度解析
前端·人工智能
张元清1 小时前
React useSessionStorage Hook:刷新不丢、只属于当前标签页的状态 (2026)
前端·javascript·react.js
cindershade2 小时前
为 TypeScript 项目建立可靠的类型边界:API 响应、表单与第三方库
前端
半仙er2 小时前
第一周02天 原型与原型链
前端