阅读视图

发现新文章,点击刷新页面。
🔲 ☆

GitHub Actions 自动化部署 Hexo 博客完全指南

注: 本文由AI生成,ednovas编辑并审核发布。

GitHub Actions 自动化部署 Hexo 博客完全指南

传统的 Hexo 博客部署流程通常是:本地写文章 → hexo clean && hexo generatehexo deploy。这个流程有几个痛点:

  • 🖥️ 换一台电脑就需要重新配置整个 Hexo 环境(Node.js、npm 依赖等)
  • 🔑 配置文件中包含 API Key 等敏感信息,直接提交到 Git 有安全风险
  • ⏱️ 每次部署都需要手动执行多个命令,繁琐且容易出错

本文将详细介绍如何使用 GitHub Actions 实现 Hexo 博客的全自动部署:推送代码到 GitHub 仓库后,自动构建并发布到 GitHub Pages,同时妥善保护敏感信息。


整体架构

本地电脑 (写文章)

│ git push → dev 分支

GitHub 仓库 (源码)

├──── GitHub Actions 自动触发
│ │
│ ▼
│ ┌─────────────────────────────────┐
│ │ GitHub Actions Runner │
│ │ │
│ │ 1. 检出代码 │
│ │ 2. 从模板 + Secrets 生成配置 │
│ │ 3. npm install │
│ │ 4. hexo generate │
│ │ 5. 部署到 gh-pages 分支 │
│ └─────────────────────────────────┘
│ │
│ ▼
│ GitHub Pages (线上博客)

└──── Cloudflare Pages 自动触发


┌─────────────────────────────────┐
│ Cloudflare Pages │
│ │
│ • dev 分支 → Preview 预览环境 │
│ • gh-pages 分支 → Production │
│ • 自动绑定自定义域名 │
│ • 全球 CDN 加速 │
└─────────────────────────────────┘

核心思路:

  • 源码存放在 dev 分支(包含模板配置文件,不含敏感信息)
  • GitHub Actions 在构建时通过 Secrets + envsubst 动态生成真实配置文件
  • 构建产物自动推送到 gh-pages 分支
  • GitHub PagesCloudflare Pages 同时从 gh-pages 分支提供服务,互为冗余
  • Cloudflare Pages 还会为每个 dev 分支的推送自动生成 Preview 预览环境
🔲 ⭐

做给 GitHub Actions 开发者用的 Actions(Part 2)

在开发 GitHub Actions 时,有时候会遇到这样的问题:如果这个 Action 接受来自用户的 GitHub token,那该如何以这个 token 背后的身份完成所需的 git 操作?

我在上一篇文章里介绍了我做的 config-git-with-token-action,专门用来解决这个问题的,但这个 Action 在配置 git 的时候又是怎么获取对应的用户名和邮箱的呢?这是通过另外一个叫做 token-who-am-i-action 来解决的。

这个 Action 可以接受用户生成的 PAT (Personal Access Token),也可以接受 GitHub App 的 token。(默认的 GitHub Actions token 当然是接受的。)它会使用 Action 调用 GitHub API 查询关于 token 自己身份相关的信息,因为类似于 Linux 的 whoami 命令所以我把它叫做 token-who-am-i-action


这个 Action 能返回的信息当中,首先要看 typeUser 还是 Bot

  • 如果是 User 的话,它所返回的其他信息包括 nameemail。这两项都可能是 undefined,因为用户可以选择隐藏自己的名称和邮箱。
  • 如果是 Bot 的话,它必须返回 nameemailappSlug,全部都是字符串,不可能是 undefined。每一个 GitHub App 必须有一个用于显示的名字,然后由此生成对外公开的地址(格式为 https://github.com/apps/${appSlug})。GitHub App 其实并不存在邮箱,但使用特定格式(${id}+${login}@users.noreply.github.com)生成的邮箱会被正确识别并显示正确的头像,例如说 GitHub Actions 默认 token 的身份使用的邮箱是 41898282+github-actions[bot]@users.noreply.github.com

除此之外,无论是哪种类型的身份,这个 Action 还能返回 loginidglobalIdlogin 对于 User 来说,就是他的个人页面地址(https://github.com/${login})中的路径,有些文档也把这个叫做 username;对于 Bot 来说,这可以由 appSlug 通过特定格式(${appSlug}[bot])生成,例如 GitHub Actions 的就是 github-actions[bot]

至于 idglobalId 分别用于 REST API 和 GraphQL。这是 GitHub 数据存储很有意思的一个地方。对于每一种 REST API 的类型(如 user),它背后都是一张独立的关系型数据表,都有一个这种类型内部唯一的 id,但这个 id 可以跟其他类型冲突。在 GraphQL 里面,因为 id 可以用来查询任何类型,所以引入了 globalId 的概念,保证即使跨类型依然唯一。GraphQL 的某些 query/mutation 的 id 默认就是 globalId;但某些必须加上 X-GitHub-Next-Global-ID: 1 的 header 进行请求才是 globalId,否则就是跨类型不唯一的 id


那我们如何在编写自己的 Action 时调用 token-who-am-i-action 呢?如果你在编写的是 composite action,可以这样写:

runs:
  using: 'composite'
  steps:
    - uses: CatChen/token-who-am-i-action@v1
      id: token-who-am-i
      with:
        github-token: ${{ inputs.github-token }}

    - shell: bash
      env:
        LOGIN: ${{ steps.token-who-am-i.outputs.login }}
        GLOBAL_ID: ${{ steps.token-who-am-i.outputs.global-id }}
        ID: ${{ steps.token-who-am-i.outputs.id }}
        NAME: ${{ steps.token-who-am-i.outputs.name }}
        EMAIL: ${{ steps.token-who-am-i.outputs.email }}
        TYPE: ${{ steps.token-who-am-i.outputs.type }}
        APP_SLUG: ${{ steps.token-who-am-i.outputs.app-slug }}
      run: |
        echo "Login is $LOGIN"
        echo "Global id is $GLOBAL_ID"
        echo "Id is $ID"
        echo "Name is $NAME"
        echo "Email is $EMAIL"
        echo "Type is $TYPE"
        echo "App slug is $APP_SLUG"

如果你编写的是 JavaScript action 可以先从 NPM 安装同名的 token-who-am-i-action 包,然后再进行调用:

import { tokenWhoAmI } from 'token-who-am-i-action';

const me = await tokenWhoAmI(githubToken);

const {
  login,
  globalId,
  type,
} = me;

if (me.type === 'User') {
  const {
    id,
    name,
    email,
  } = me;
} else if (me.type === 'Bot') {
  const {
    appSlug,
    id,
    name,
    email,
  } = me;
}

希望这个 Action 对各位 GitHub Actions 开发者有用。非 Actions 开发者也可以直接在 Workflow 里面使用这个 Action,如果你的 Workflow 使用非默认的 GitHub Actions(机器人)身份进行 git 操作的话。大家在使用过程中遇到什么问题,或者是希望增加什么新功能,欢迎到项目的 GitHub 开 issue。

🔲 ⭐

做给 GitHub Actions 开发者用的 Actions(Part 1)

在开发 GitHub Actions 时,有时候会遇到这样的问题:如果这个 Action 接受来自用户的 GitHub token,那该如何以这个 token 背后的身份完成所需的 git 操作?

使用 token 操作 GitHub API 是很容易的,通过 @actions/github(或 @octokit/core)创建一个 Octokit 实例时把 token 传进去就可以了,之后通过这个实例进行的所有 API 调用(包括 REST 和 GraphQL)都会以这个 token 的身份进行。但命令行的 git 操作怎么办呢?如何让 git commit 的作者变成 token 背后的身份?如何让 git push 以 token 背后的身份进行提交?(这两者并不一定要用同一个身份。)我做了 config-git-with-token-action 就是用来解决这个问题的。

这个 Action 会对 ghgit 进行配置,让它们的身份信息变成 GitHub token 背后的身份信息。gh 的配置相对简单一些,把 GH_TOKEN 这一环境变量配置好就行了,然后执行 go auth status 就能打印出 gh 认为自己在使用的身份信息。对 git 进行配置稍微麻烦一些,gh auth setup-git 只能保证 git 在跟 GitHub 交互时从 gh 获得身份信息,但并不指明具体是哪一个身份。为了保证 git commit 使用正确的身份,需要通过 git config 来设置正确的用户名和邮箱。此外,为了保证 git push 使用正确的身份,需要通过 git remote set-url origin 来更新上游地址,在 https://github.com/… 的地址中注入用户名和 token,让它变成 https://username:token@github.com/…

这个 Action 假设用户在执行它之前已经执行过了 @actions/checkout,所以它不会尝试自行建立项目目录。大家最好使用同一个 token 进行 @actions/checkout,这样项目目录从一开始就是以 token 背后的身份创建的。以下是一个完整的用例:

runs:
  using: ‘composite’
  steps:
    - uses: actions/checkout@v4

    - uses: CatChen/config-git-with-token-action@v1
      with:
        github-token: ${{ inputs.github-token }}

    - shell: bash
      run: |
        echo “Set up git user name: $(git config —get user.name)”
        echo “Set up git user email: $(git config —get user.email)”
        echo “Set up git remote origin with login and token: $(git remote get-url origin)”

    - shell: bash
      run: |
        touch test_file
        git commit test_file -m ‘Created test file’
        git push

做为 GitHub Actions 开发者,如果你利用 JavaScript 而非 bash 进行开发,那上述 composite action 的用例并不适用,我们需要一个针对 JavaScript action 的用例。我自己有同样的需求,所以 config-git-with-token-action 同时还是一个 NPM 包,可以在 JavaScript 中进行调用获得同样的功能。(这个包具备完整的 TypeScript 类型信息。)安装好之后,通过 JavaScript 调用的用例如下:

import { configGitWithToken } from ‘config-git-with-token-action’;

await configGitWithToken(githubToken);

希望这个 Action 对各位 GitHub Actions 开发者有用。非 Actions 开发者也可以直接在 Workflow 里面使用这个 Action,如果你的 Workflow 使用非默认的 GitHub Actions(机器人)身份进行 git 操作的话。大家在使用过程中遇到什么问题,或者是希望增加什么新功能,欢迎到项目的 GitHub 开 issue。

至于这个 Action 是如何获取到 token 背后的身份信息的,那是这个系列的下一篇文章要介绍的下一个 Action 负责的。

🔲 ⭐

怎么用 Github Actions 部署 Next.js 项目到服务器

怎么用 Github Actions 部署 Next.js 项目到服务器?

最近开发一个项目 Next.js,部署到 Vercel 上在国内访问速度和稳定性都不太好,所以想部署到自己的服务器上。经过实践后,分享一下怎么用 Github Actions 部署 Next.js 项目到 Linux 服务器上。

服务器环境配置

在服务器要安装 Node.js 和 PM2,然后配置 Nginx。

安装 Node.js

推荐使用 fnm 来安装 Node.js,以后可以方便的使用不同版本的 Node.js。

1
curl -fsSL https://fnm.vercel.app/install | bash

安装好后重新 SSH 连接到服务器终端,用 fnm 安装 Node.js。

1
fnm install 18

安装 PM2

我们使用 PM2 来管理 Node.js 进程。以便在服务器重启后自动启动项目。

用 npm 安装 PM2:

1
npm install pm2 -g

Nginx 配置

Node.js 项目默认运行在 3000 端口,我们使用 Nginx 来反向代理到 3000 端口。这样就可以使用 80 或 443 端口来访问项目。

在 nginx 配置文件夹 /etc/nginx/sites-available 中创建一个配置文件 example.com

1
2
3
4
5
6
7
8
9
10
11
12
server {
listen 80;
server_name example.com;

location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
}

然后在 sites-enabled 文件夹中创建一个软链接。

1
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/example.com

然后重新加载 Nginx 配置文件:

1
sudo nginx -s reload

生成 SSL 证书

安装 certbot 来生成证书并修改 Nginx 配置文件以使用证书。

生成证书:

1
sudo certbot  --nginx

命令执行后会提示输入邮箱,然后选择同意条款,最后选择要生成证书的域名,然后就会自动的生成证书并修改 Nginx 配置文件和重新加载。

代码拉取 SSH 密钥配置

  1. 生成 SSH 密钥

  2. 把公钥添加到 Github 仓库的 Deploy keys 中

    添加公钥

  3. 把私钥到放到服务器的用于发布项目的用户的 ~/.ssh/id_rsa 路径,以便 Github Actions 用 SSH 连接服务器拉取 Github 上的代码。

    1
    echo "private-key" >> ~/.ssh/id_rsa

Github Actions 配置

我们使用 appleboy/ssh-action@master 这个 Github Action 用 ssh 来连接服务器,然后执行命令来部署项目。为了安全起见,我们把服务器 host 和端口,还有 ssh 密钥都配置到 Github 仓库的 Secrets 中,这样就不会把服务器的信息暴露到配置文件中。

准备部署 SSH 密钥

  1. 生成 SSH 密钥对用于发布项目

  2. 把公钥添加到服务器的 ~/.ssh/authorized_keys 文件中

    1
    echo "public-key" >> ~/.ssh/authorized_keys
  3. 在 Github 仓库的 Settings -> Secrets -> Actions 中添加名为 key 的 secret,值为私钥。

    添加私钥

设置 host 和端口

同样在 Github 仓库的 Settings -> Secrets -> Actions 中添加 hostport

创建 Github Actions 配置文件

在仓库的根目录中创建文件夹 .github/workflows。在此文件夹中创建一个 .yml 后缀的文件。

在配置文件中指定main分支在 push 时触发 Github Actions,然后指定一个 job 名为 deploy,在这个 job 中使用 appleboy/ssh-action@master 这个 Github Action 来连接服务器,然后执行部署脚本。在 Github Actions 中可以使用 Secrets 中的变量,这样就不会把服务器的信息暴露到配制文件中。

部署脚本首先用 git clone 拉取代码, 然后安装依赖,最后使用 PM2 启动项目。最后用 pm2 save 保存项目配置,以便在服务器重启后自动启动项目。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.host }}
username: ${{ secrets.username }}
key: ${{ secrets.key }}
port: ${{ secrets.port }}
script: |
git clone ${{ github.repository }} /home/username/project
cd /home/username/project
npm install
pm2 start npm --name "project" -- start
pm2 save

此后每次 push 到 main 分支时,Github Actions 就会自动部署项目到服务器上。

参考

🔲 ☆

境内外分离并分离图片源的 Hexo 博客配置

<p>每年寒暑假,我都会去倒腾倒腾博客的搭建问题,上一次的全站加速存在着可能的几个问题:</p><ul><li>对于更新的同步来说太慢了,导致修改后还得手动到后台“刷新预热”处清除各部署单元的缓存</li><li>全站加速的境内境外分别计费,资源包不互通</li><li>在博客访问少的时候,CDN 节点也得回源到 GitHub,还是挺慢的</li></ul><p>所以有没有一种方法,对内对外都可以用起来很方便呢?在研究域名解析的负载均衡后,我选择了国内国外分开解析的方法——国内直接解析到自己的服务器,国外解析到 GitHub Pages,就可以了。</p><p><img src="/../images/2022073001.png" alt="DNSPods中设置境内境外分别的解析"></p><p>一开始觉得,这有什么难的呀,改改解析就完事了,后来发现博客的图包太大了,部署起来费事(因为从 GitHub 克隆仓库,带宽时高时低),再加上服务器是 1M 小水管儿,所以这次我用上了云服务中吹来吹去的<strong>对象存储</strong>来托管图片的存储<del>(对哦,没有对象存就 new 一个诶)</del>。</p><p><img src="/../images/2022073002.png" alt="腾讯云的对象存储"></p><p>顺便在这里提一句,腾讯云的对象存储对于新用户赠送 180 天 50GB 的存储(但是流量费、请求费少不了),但是我看了一下我的免费额度,好像对于我当时那批用户,腾讯云给的额度好像不局限于存储诶,还是永久免费的。庆幸高一的时候,我就已经整起腾讯云这东西了 23333。</p><p><img src="/../images/2022073003.png" alt="我的资源包免费额度"></p><p>对象存储首先需要在云服务商控制台创建桶,设置好权限(记得设置防盗链,以及关闭空 referer 支持),设置好自定义域名(其实是换 cdn 方便,因为到时候只要改解析就好了,不过坏处是要设置 SSL 证书,各有利弊吧)。</p><p>由于本站的图片都在<code>/images/</code>目录下,于是一个非常简单的想法就是把这个目录 302 出去。</p><figure class="highlight plaintext"><table><tbody><tr><td class="code"><pre><span class="line">location /images/ {</span><br><span class="line"> rewrite ^/(.*) https://imgcdn.icys.top$request_uri redirect;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>因为我也不喜欢存储桶里图片堆在根目录下(不好清理),所以我在存储桶里还是建立了一个<code>images</code>目录,这样在跳转的时候就省事了。</p><p>但是国内外如果我都使用国内节点可能带来国外图片速度下降问题,再加上 Pages 也没有 302 支持,于是还得考虑一下国外的图片处理问题。然后我发现,好像我可以选择建一个叫做<code>images</code>的仓库,开启 Pages 后就自然而然的就是<code>icy-blue.github.io/images/</code>(配置 CNAME 之后就是<code>icys.top/images/</code>),自然而然地就可以不用 302 了,真的是方便。</p><img src="../images/2022073004.png" alt="images仓库的Pages" style="zoom:50%;"><p>于是,对于图片的处理,可以在本地<code>images</code>目录新建一个仓库与 GitHub 同步,这样本地编辑 markdown 的时候写<code>../images/xxx</code>也能找得到,计划再在<code>images</code>仓库设置<code>Action</code>对新图片自动同步到腾讯云,这样就实现了一个比较优秀的图片加载问题。</p><hr><p>对了,刚打算收工,就发现 GitHub 对于 Pages 的更新<a href="https://github.blog/changelog/2022-07-27-github-pages-custom-github-actions-workflows-beta/">GitHub Pages: Custom GitHub Actions Workflows (beta)</a>。大概说的是,Pages 的输入可以不是一个分支了,可以直接用 Action 打好的 tar 包去部署,省的像我,先建立一个<code>icy-blog</code>仓库,再通过 Action 生成到<code>icy-blue.github.io</code>仓库,就可以一步到位啦。看看暑假有没有空做,不行等寒假再搞搞~</p><p>另外,之前好像挖了几个坑来着,填填坑——</p><ul><li>使用 GitHub Actions 实现仓库从 GitHub 到 Gitee 的同步:这个 Gitee 好像自己就可以实现,也省的自己写了</li><li>使用 Gitee Pages:现在 Gitee 的开源环境还是算了吧,另外 Gitee Pages 是要身份证核验的</li><li>做一个合适的负载均衡:境内外的负载均衡可能也算负载均衡吧</li><li>使用 Lint-md 让博客内容排版更加规范:这个好像可以简单提一下,用的是<a href="https://github.com/lint-md/lint-md">llint-md</a>的仓库,用起来也非常轻松,在 Action 部署的时候 npm 安装好,然后把 markdown 文件送到参数里(我用了 shell 里<code>xargs</code>的一个方法使得一行就弄完了,不过注意一下当时我设置的时候项目路径有大小写 bug 所以我用的 Windows 下的 Bash Shell,也许现在 bug 修了罢),最后别忘了创建提交改回去(要在设置里给 Action 写权限)</li></ul><figure class="highlight yaml"><table><tbody><tr><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Import</span> <span class="string">lint-md</span></span><br><span class="line"> <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string"> npm update npm</span></span><br><span class="line"><span class="string"> npm i -g @lint-md/cli</span></span><br><span class="line"><span class="string"></span> </span><br><span class="line"><span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Fix</span> <span class="string">format</span> <span class="string">of</span> <span class="string">markdown</span> <span class="string">sources</span></span><br><span class="line"> <span class="attr">shell:</span> <span class="string">bash</span></span><br><span class="line"> <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"> <span class="string">find</span> <span class="string">./source/</span> <span class="string">-name</span> <span class="string">"*.md"</span> <span class="string">|</span> <span class="string">xargs</span> <span class="string">lint-md</span> <span class="string">--fix</span> </span><br></pre></td></tr></tbody></table></figure><img src="../images/2022073005.png" alt="在Setting-Action-General中开启Action的写许可" style="zoom:50%;">
🔲 ⭐

利用GitHub Actions实现版本自动构建与发布流程

GitHub Actions 是 GitHub 自家推出的持续集成和持续交付工作流服务。自从上次利用GitHub Actions实现Blog自动部署与发布过后,构建和发布blog从此变得轻松。这次,我打算充分利用 GitHub Actions, 把开源项目的持续构建和发布流程做成完全自动化。

用我经常维护的一个项目 GoDNS 举个栗子,每次发布的时候,需要手动执行的两个任务:

  • 基于不同平台进行交叉编译,把编译好的二进制文件分别上传到GitHub上,进行新版本的发布。
  • 另外,这个项目还需要构建基于不同平台的 Docker 镜像,然后推送到 Docker Hub 上。

在使用 GitHub Actions 之前,我的做法就是在本机使用 Makefile 分别构建二进制文件和 Docker 镜像。这一系列操作在 Mac OS 下的结果就是:无法避免的风扇狂转,以及CPU温度的陡然上升。为了减轻笔记本的负担,后来我把构建 Docker 镜像的流程迁移到了VPS上。不过,这也使发布流程略显繁琐。

有了 GitHub Actions,事情就变得简单了,通过设置一个触发流程的事件,就可以用两个并行的流程分别构建二进制文件和 Docker 镜像了:

GoDNS 的构建流程中,触发条件是当每次有新的 tag 被创建的时候。通过把新创建的 tag push 到GitHub,两个 actions 就会并行执行。其中一个 action 负责构建跨平台二进制文件,完成后自动 release 一个相应的新版本。另外一个 action 负责构建 Docker 镜像,并 push 到 Docker Hub。

不得不说,自从用上了 GitHub Actions,我的笔记本被彻底解放了。附上两个自动构建发布流程,供参考。

Workflow 及定义

  1. 二进制包自动构建与发布流程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
name: Auto Release

on:
push:
tags:
- '*'

permissions:
contents: write

jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
-
name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.17
-
name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
# either 'goreleaser' (default) or 'goreleaser-pro'
distribution: goreleaser
version: latest
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.MY_GITHUB_TOKEN }}
  1. Docker 镜像自动构建与发布流程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
name: Docker Image CI

on:
push:
tags:
- '*'

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Get git tag
id: tag
uses: dawidd6/action-get-tag@v1
with:
strip_v: true
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push the docker image
uses: docker/build-push-action@v2
with:
platforms: linux/amd64,linux/arm64,linux/arm/v7
push: true
tags: timothyye/godns:latest,timothyye/godns:${{steps.tag.outputs.tag}}
🔲 ☆

基于 GitHub Actions + 宝塔 + DCDN 的 Hexo 博客配置

<p>不知不觉博客已经开启接近两年了,一直以来本站使用的技术是阿里云全站加速+GitHub Pages 的部署方式。不过由于国内 CDN 回源 Pages 服务器还是存在着连接质量差的问题,在两周年之际,本站使用了基于 GitHub Actions + 宝塔 + 阿里云全站加速的搭建模式。</p><h2 id="好用的-GitHub-Actions"><a href="#好用的-GitHub-Actions" class="headerlink" title="好用的 GitHub Actions"></a>好用的 GitHub Actions</h2><p>Actions 是个好东西,一个月有 3000 分钟的私有仓库时长配额(GitHub Pro 或 GitHub Student Pack),普通用户也有 2000 分钟每月的私有仓库时长配额。值得一提的是,<strong>对于公开的仓库,GitHub Actions 是全免费、随便用的</strong>(单帐户同类镜像仅允许同时开启一个,否则会排队等待上一个 Job 执行完毕,单 Job 运行最长时间 6 小时)。</p><p>相比于<code>Travis CI</code>之类的持续集成工具来说,GitHub Actions 对同平台的仓库相比更加具有便利性(至少 clone 个代码是真心快<span class="github-emoji"><span>😂</span><img src="https://github.githubassets.com/images/icons/emoji/unicode/1f602.png?v8" aria-hidden="true" onerror="this.parent.classList.add('github-emoji-fallback')"></span>)。</p><p>Actions 不只是可以做 Release 导出、项目构建之类的操作,由于其定时运行的特性,常被大家用来渲染账户成就(如 star 超过 3k 的<a href="https://github.com/lowlighter/metrics">Metrics</a>,作者 icy 也部署了其自动生成<a href="https://github.com/icy-blue">个人 Profile</a>),<a href="https://github.com/zhangt2333/actions-SduElectricityReminder">自动体温填报</a>等操作。</p><h2 id="Hexo-类博客与-GitHub-Pages"><a href="#Hexo-类博客与-GitHub-Pages" class="headerlink" title="Hexo 类博客与 GitHub Pages"></a>Hexo 类博客与 GitHub Pages</h2><p>Hexo 是目前来说非常普遍使用的博客了,在 GitHub 上有许多主题,如本站使用的是<a href="http://blinkfox.com/">闪烁之狐</a>的<a href="matery">matery</a>主题,在此也再次向作者表示感谢。用户可以轻易通过几行代码就可以生成一个简单的博客,编写 markdown 的博客内容,并通过一句简单的<code>hexo deploy</code>或者<code>hexo d</code>就可以将自己博客部署到仓库中。</p><p>虽然 Hexo 最近一直在更新,不过 Hexo 很多功能插件的依赖项,爆出了安全漏洞,如<code>hexo-renderer-marked</code>使用的<code>marked@^2.1.3</code>,爆出了<a href="https://github.com/advisories/GHSA-5v2h-r2cx-5xgj">GHSA-5v2h-r2cx-5xgj</a>和<a href="https://github.com/advisories/GHSA-rrrm-qjm4-v8hf">GHSA-rrrm-qjm4-v8hf</a>安全漏洞,而截至目前(2022 年 1 月 21 日)插件维护方尚未对该插件的依赖版本升级至<code>4.0.10</code>以上。</p><p>为了更加的傻瓜式,GitHub 还提供了 Pages 服务,帮助直接将一个静态网站部署到<code>https://&lt;username&gt;.github.io/&lt;repository&gt;/</code>上。经过一段时间的自动部署,我们就可以访问自己刚刚部署好的博客界面了。</p><p>GitHub Pages 可以满足绝大多数海外用户的使用需求,因为 Pages 对于一个简单的非商业项目来说配额已经十分充足——</p><blockquote><p>GitHub Pages sites are subject to the following usage limits:</p><ul><li>GitHub Pages source repositories have a recommended limit of 1GB. </li><li>Published GitHub Pages sites may be no larger than 1 GB.</li><li>GitHub Pages sites have a <em>soft</em> bandwidth limit of 100GB per month.</li><li>GitHub Pages sites have a <em>soft</em> limit of 10 builds per hour.</li></ul></blockquote><p>配额说明来自于 2022 年 1 月 21 日的<a href="https://docs.github.com/cn/pages/getting-started-with-github-pages/about-github-pages#usage-limits">GitHub Docs</a>,不过对于国内用户来说更大的问题是,由于海外带宽的限制以及一些原因,国内用户访问 GitHub 及 GitHub Pages 经常出现连接问题。自己好不容易搭建的博客,发现国内的朋友们,尤其是不太会科学使用网络的朋友们,打不开自己的博客,可能也会非常沮丧吧<span class="github-emoji"><span>😢</span><img src="https://github.githubassets.com/images/icons/emoji/unicode/1f622.png?v8" aria-hidden="true" onerror="this.parent.classList.add('github-emoji-fallback')"></span><span class="github-emoji"><span>😱</span><img src="https://github.githubassets.com/images/icons/emoji/unicode/1f631.png?v8" aria-hidden="true" onerror="this.parent.classList.add('github-emoji-fallback')"></span>。</p><img src="../images/2022012101.jpg" alt="真·二次元" style="zoom: 33%;"><h2 id="使用-CDN-分发与加速"><a href="#使用-CDN-分发与加速" class="headerlink" title="使用 CDN 分发与加速"></a>使用 CDN 分发与加速</h2><p>既然 GitHub Pages 打不开,我们可能就想,有没有其他更合适的办法,让大陆用户正常地访问自己的网页呢?当然有——icy 和他的朋友<a href="https://www.kskun.com/">KS</a>就不约而同地使用了 CDN 分发和加速。内容分发网络 CDN(Content Delivery Network)可以将用户的请求负载均衡到不同的缓存节点,当用户的请求到达时,CDN 将判断访问者 IP,将请求按优先级分发给源站或最近缓存节点,以加快用户的请求速度。</p><p>以当前的场景来说,在网站设置了 CDN(设置解析、源站、设置缓存目录及后缀、设置缓存过期时限、配置 HTTPS 等)后,用户请求到 CDN 时,CDN 会先判断是否存在缓存,有缓存将请求转给缓存,没有缓存会由 CDN 请求源站,按照配置进行缓存,并将结果返回给用户。由于 CDN 和 Pages 的连接是畅通的,用户和 CDN 的连接是畅通的,于是通过 CDN 作为跳板,实现了 GitHub Pages 的高速访问。自然,也付出了 CDN 的使用费用(见<a href="./CDN%E6%98%AF%E4%BB%80%E4%B9%88%E5%8F%AF%E4%BB%A5%E5%90%83%E5%90%97.md">CDN 是是什么可以吃吗</a>)。</p><p>值得注意的是,如果使用国内 CDN,且对国内用户提供服务时,要求域名进行 ICP 备案。</p><h2 id="使用服务器部署博客"><a href="#使用服务器部署博客" class="headerlink" title="使用服务器部署博客"></a>使用服务器部署博客</h2><p>既然使用国内 CDN 了,何不直接使用国内的服务器呢~</p><p>在 2021 年双十一期间<del>(活动已结束,价格供参考)</del>,腾讯云打出了 2 核 4GB 8Mbps 月流量 1200GB 的轻量应用服务器一年 70 元、三年 198 元的活动,吸引了许多建站开发者。按购买 3 年计算,月均消费 5.5 元,是非常适合学生党“折腾”的。</p><img src="../images/2022012103.png" alt="腾讯云 2021 年双十一活动界面" style="zoom: 67%;"><p>一般来说,我们的代码编辑仍然还是在自己的电脑上,而服务器仅仅是对于 Hexo 生成的静态文件的展示(一般来说不会在服务器上使用 Node.js 展示 Hexo 服务,因为对于静态网站,在线解析生成其实是一种浪费)。此时我们其实就可以使用上面所说的<code>hexo d</code>命令,把静态网站部署到<code>Gitee</code>上(因为服务器去访问 GitHub 还是非常困难的),然后在服务器上同步拉取即可。</p><p>同步拉取的方式,最简单的办法就是在服务器上设置循环脚本,每隔一段时间拉取仓库`。</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">git fetch --all </span><br><span class="line">git reset --hard origin/&lt;branch&gt; </span><br><span class="line">git pull</span><br></pre></td></tr></tbody></table></figure><p>由于 Hexo 的部署是强制推送,不存储过往的界面,以减少整个仓库的大小,我们在拉取仓库的时候使用强制覆盖本地仓库的方式进行更新。考虑到 SSH 会话运行的程序在 SSH 连接断开后不太稳定,我们可以使用终端复用器 Tmux(terminal multiplexer),使得脚本在后台使用。</p><p>关于 Tmux,在<a href="https://www.ruanyifeng.com/blog/2019/10/tmux.html">阮一峰博客</a>那里有一个很好的介绍,我们可以通过<code>tmux new -s &lt;session-name&gt;</code>创建一个新的会话,然后在会话里运行 Python 脚本,一个简单的 Python 脚本可能像这样:</p><figure class="highlight python"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"><span class="keyword">import</span> threading</span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line"> time.sleep(<span class="number">60</span>) <span class="comment"># 60 seconds</span></span><br><span class="line"> os.system(<span class="string">"git fetch --all"</span>)</span><br><span class="line"> os.system(<span class="string">"git reset --hard origin/master"</span>)</span><br><span class="line"> os.system(<span class="string">"git pull"</span>)</span><br></pre></td></tr></tbody></table></figure><p>当然读者也可以通过写 bash 脚本解决问题。我们在刚刚建立的 session 中运行脚本后关闭 SSH 会话或按下<code>Ctrl + B d</code>将会话切回到后台,即可实现脚本的后台运行。</p><h2 id="WebHook"><a href="#WebHook" class="headerlink" title="WebHook"></a>WebHook</h2><p>循环拉取仓库好像挺傻的,不美观,那能不能让仓库主动去推送更新消息呢?WebHook 可以帮助我们了解这样的信息。</p><p>WebHook 是一种 API 概念,当仓库有变动时(新 Push、新 PR、新 Issue 等等),代码托管平台会给仓库预留的链接发送 POST 请求。我们一般在服务器的某个端口,监听这类的 POST 请求(用 Node.js、Python 等程序可以很快地编写一个监听 POST 请求的 Server,Python + Flask 的实现可以参考<a href="https://www.cnblogs.com/Alin-2016/p/7422987.html">python+flask:实现 POST 接口功能</a>,Go 语言可以参考<a href="https://blog.csdn.net/qq_27312939/article/details/110632297">GO 接收 GET/POST 参数以及发送 GET/POST 请求</a>,Node.js 可以参考<a href="https://blog.csdn.net/w390058785/article/details/79770540">【node.js】处理前端提交的 POST 请求</a>,其他语言不再列举)。根据<a href="https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads">GitHub</a>和<a href="https://gitee.com/help/articles/4271">Gitee</a>的文档,我们可以在服务器上解析平台上发生的事件,并进行一定的处理。</p><p>以监测平台有新提交自动拉取代码这个需求来说,我们可以设置相关的触发器,设置相关的钩子地址,并设置鉴权(以免其他用户滥发),当代码托管平台的仓库有了新提交,我们的服务器则会收到 POST 消息,以便实现。</p><p>作为 POST 的回应,建议在服务器上回复 JSON,并设置<code>HTTP status 200</code>,以免部分平台认定推送失败。</p><h2 id="宝塔的-WebHook"><a href="#宝塔的-WebHook" class="headerlink" title="宝塔的 WebHook"></a>宝塔的 WebHook</h2><p>不愿意自己写代码监听?宝塔软件商店里的<code>宝塔WebHook</code>可以帮你实现 WebHook 的接收。</p><p>在宝塔的软件商店中,搜索<code>WebHook</code>,找到<code>宝塔WebHook</code>,便可以在里面添加钩子。</p><img src="../images/2022012104.png" alt="宝塔 WebHook 界面" style="zoom: 80%;"><p>添加完后,我们可以看到自己添加钩子的情况,包括钩子名称、添加时间、近期调用、调用次数等信息。我们还可以查看密钥查看钩子的密钥,以便鉴权。在查看密钥的界面,宝塔提供了一个示例的链接,其中参数包括密钥和脚本参数,我们可以按需进行调整,并把最后的链接放在代码仓库-设置-WebHook 的相应位置(由于我们的鉴权密钥在 URL 的参数上,所以对于 GitHub 和 Gitee 来说,密码处可以随便写)。</p><p><img src="/../images/2022012105.png" alt="宝塔WebHook界面"></p><p><img src="/../images/2022012106.png" alt="宝塔WebHook查看密钥界面"></p><p>下面是 GitHub 和 Gitee 的 WebHook 设置示例:</p><img src="../images/2022012107.png" alt="GItHub的WebHook设置示例" style="zoom: 50%;"><img src="../images/2022012108.png" alt="Gitee的WebHook设置示例" style="zoom:50%;"><p>做到这里,我们就可以让服务器自动跟进代码仓库的更新了。</p><h2 id="使用-GitHub-Actions-实现静态界面的生成"><a href="#使用-GitHub-Actions-实现静态界面的生成" class="headerlink" title="使用 GitHub Actions 实现静态界面的生成"></a>使用 GitHub Actions 实现静态界面的生成</h2><p>每次我们发现,自己的静态界面确实已经保存在代码仓库中了,而原始文件却没能保存。有的朋友会选择再建一个仓库专门提交原始文件,先提交静态界面,再提交原始文件,提交两次可能总是会遗漏,那有没有只提交一次的办法呢?</p><p>答案自然是肯定的,我们可以通过使用 GitHub Actions 实现静态界面的生成。</p><p>下面是一个简单的配置文件,修改用户名邮箱等信息后,将其放在原始博客仓库的<code>.github/workflows/</code>内,以 yml 格式为文件名后缀,配置就已经部署好了——吗?</p><figure class="highlight yaml"><table><tbody><tr><td class="code"><pre><span class="line"><span class="attr">name:</span> <span class="string">Hexo</span> <span class="string">Deploy</span></span><br><span class="line"></span><br><span class="line"><span class="attr">on:</span></span><br><span class="line"> <span class="comment"># 如果你的默认 branch 是 master,将下面的 main 改为 master</span></span><br><span class="line"> <span class="attr">push:</span></span><br><span class="line"> <span class="attr">branches:</span> [ <span class="string">main</span> ]</span><br><span class="line"> <span class="attr">pull_request:</span></span><br><span class="line"> <span class="attr">branches:</span> [ <span class="string">main</span> ]</span><br><span class="line"> <span class="attr">workflow_dispatch:</span></span><br><span class="line"></span><br><span class="line"><span class="attr">jobs:</span></span><br><span class="line"> <span class="attr">deploy-github:</span></span><br><span class="line"> <span class="attr">runs-on:</span> <span class="string">ubuntu-latest</span></span><br><span class="line"></span><br><span class="line"> <span class="attr">steps:</span></span><br><span class="line"> <span class="comment"># 设置服务器时区为东八区 </span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Set</span> <span class="string">time</span> <span class="string">zone</span></span><br><span class="line"> <span class="attr">run:</span> <span class="string">sudo</span> <span class="string">timedatectl</span> <span class="string">set-timezone</span> <span class="string">'Asia/Shanghai'</span></span><br><span class="line"></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Checkout</span> <span class="string">source</span></span><br><span class="line"> <span class="attr">uses:</span> <span class="string">actions/checkout@v2.4.0</span></span><br><span class="line"></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Setup</span> <span class="string">Node.js</span></span><br><span class="line"> <span class="attr">uses:</span> <span class="string">actions/setup-node@v1</span></span><br><span class="line"> <span class="attr">with:</span></span><br><span class="line"> <span class="attr">node-version:</span> <span class="string">'12'</span></span><br><span class="line"></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Setup</span> <span class="string">Hexo</span></span><br><span class="line"> <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string"> npm install hexo-cli -g</span></span><br><span class="line"><span class="string"> npm install hexo-deployer-git --save</span></span><br><span class="line"><span class="string"> npm install</span></span><br><span class="line"><span class="string"></span> </span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Setup</span> <span class="string">Git</span></span><br><span class="line"> <span class="attr">env:</span></span><br><span class="line"> <span class="attr">ACTION_DEPLOY_KEY:</span> <span class="string">${{</span> <span class="string">secrets.HEXO_DEPLOY_KEY</span> <span class="string">}}</span></span><br><span class="line"> <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string"> mkdir -p ~/.ssh/</span></span><br><span class="line"><span class="string"> echo "$ACTION_DEPLOY_KEY" &gt; ~/.ssh/id_rsa</span></span><br><span class="line"><span class="string"> chmod 700 ~/.ssh</span></span><br><span class="line"><span class="string"> chmod 600 ~/.ssh/id_rsa</span></span><br><span class="line"><span class="string"> ssh-keyscan github.com &gt;&gt; ~/.ssh/known_hosts</span></span><br><span class="line"><span class="string"> git pull</span></span><br><span class="line"><span class="string"> git config --global user.email "&lt;email&gt;"</span></span><br><span class="line"><span class="string"> git config --global user.name "&lt;username&gt;"</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="comment"># 如果在自己的_config.yml已配置,则直接运行最后两条命令即可</span></span><br><span class="line"> <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Deploy</span></span><br><span class="line"> <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string"> cat &gt;&gt; _config.yml &lt;&lt;EOF</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"> <span class="comment"># 自动部署配置</span></span><br><span class="line"> <span class="attr">deploy:</span></span><br><span class="line"> <span class="attr">type:</span> <span class="string">git</span></span><br><span class="line"> <span class="attr">repo:</span> <span class="string">git@github.com:&lt;username&gt;/&lt;username&gt;.github.io.git</span></span><br><span class="line"> <span class="attr">branch:</span> <span class="string">hexo</span></span><br><span class="line"></span><br><span class="line"> <span class="string">EOF</span></span><br><span class="line"> <span class="string">hexo</span> <span class="string">clean</span></span><br><span class="line"> <span class="string">hexo</span> <span class="string">deploy</span></span><br></pre></td></tr></tbody></table></figure><p>去部署一个仓库,我们当然是要提供一些权限让它有权更改目的仓库,我们到目的仓库<code>&lt;username&gt;.github.io</code>的设置界面,找到<code>Deploy keys</code>后在右上角添加一对公钥(为了避免后期权限混乱的麻烦,建议新建一对新的公钥私钥对,公钥私钥的生成方式可以参考<a href="https://gitee.com/help/articles/4181">Gitee 的帮助文档</a>),将公钥放在<code>Key</code>区域,标题任取即可,<strong>注意勾选<code>Allow write access</code>,否则仅有只读权限,仍然无法部署。</strong></p><p><img src="/../images/2022012109.png" alt="添加仓库部署可信公钥"></p><p>现在目的仓库就认可了这个公钥,接下来我们要把私钥交给源仓库。直接放在仓库内是不保险的,因为我们 clone 了这个仓库就可以看到私钥明文,即便是私有仓库但是如果存储该仓库的主机被攻破,仍然可以得到私钥的明文。因此我们应该放在一个更加合适的位置,就比如仓库设置中的<code>Secret</code>区域。</p><p><img src="/../images/2022012110.png" alt="仓库 Secret 区"></p><p>我们在右上角添加新密钥,命名为<code>HEXO_DEPLOY_KEY</code>(如修改需要同步修改上面 yml 文件的名称)。密钥粘贴时,需将<code>-----BEGIN OPENSSH PRIVATE KEY-----</code>之类的标识行一同复制,在<code>-----END OPENSSH PRIVATE KEY-----</code>后最好换一行(即光标在密钥下一行)。</p><p><strong>请注意,密钥写入后无法查看,只能覆盖,如丢失需要重新生成公钥私钥对。</strong></p><p><img src="/../images/2022012111.png"></p><p>此时,用于部署的仓库就有了部署权限,在每次用户的 Push 操作和 PR 操作后,可以把生成的静态目录推送的目标仓库了。</p><h2 id="放在最后"><a href="#放在最后" class="headerlink" title="放在最后"></a>放在最后</h2><p>篇幅有点长了,剩下的当作下期预告吧——</p><ul><li><p>使用 GitHub Actions 实现仓库从 GitHub 到 Gitee 的同步</p></li><li><p>使用 Gitee Pages</p></li><li><p>做一个合适的负载均衡</p></li><li><p>使用 Lint-md 让博客内容排版更加规范</p></li></ul>
❌