记一次倒腾 GitHub Actions 的经历

以下故事提醒我们,凡事要做好预案,有备无患,切忌盲人摸象、管中窥豹。

事情起因是:有台湾朋友提示我的译文转换为繁体后,出现了偏离原意的现象(例如,skin “皮肤”被误认为 panel“面板”)。我的同事分析认为和 OpenCC 未及时更新有关,但如何使 OpenCC 升级到最新版本成为了棘手的麻烦。那么我们做了哪些动作?

零、背景介绍

如前文所述,我早前加入了 Project Trans 志愿团体并参与外文翻译项目;在同事帮助下,这批译文通过 GitHub Actions 构建为网页,后上传到 CloudFlare 向公众开放。原 Actions 脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
jobs:
deploy:
runs-on: ubuntu-22.04
if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')"
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: peaceiris/actions-hugo@v2
with:
hugo-version: latest
extended: true
- run: sudo apt-get install opencc
- run: npm ci
- run: ./scripts/opencc.sh
- run: hugo --minify
env:
HUGO_DISABLELANGUAGES: 'en'
- run: npx wrangler pages deploy public --project-name tfsci --branch main
if: github.ref == 'refs/heads/main'
env:
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CF_API_TOKEN }}

脚本中先拉取仓库原稿,准备 Node.js 和 Hugo 环境,再通过 Ubuntu 软件库拉取 OpenCC 进行简繁转换,最后构建网站并上传。
问题根源在于,在 Ubuntu 22 (Jammy) 软件库中,OpenCC 停滞于两年前的 1.1.3 版本,早于“皮肤”转换问题的修复时间——况且当下最新版已至 1.1.7。

一、引入新版系统软件库?

我和同事查询发现,Ubuntu 23 (Lunar/Mantic) 软件库可提供 OpenCC 1.1.7,同事遂提出将 Ubuntu 23 引入脚本里。但遗憾的是,GitHub 仅提供两个长期支持版(即 20 和 22),预计引入不会成功,只好作罢。

随后同事又提出,指定新版 OpenCC 的下载地址并尝试安装;但这有个隐患,即 Ubuntu 23 作为短支持版本,在 24 版推出后将很快停止支持,其软件库也将下线,届时仍需重新指定 OpenCC 下载地址。当然,也有缓兵之计:静待 Ubuntu 24 推出并被 GitHub 引入,但这要等到 2024 年一季度。我们自然不愿坐以待毙。

二、Docker 环境中构建?

这是我最初提出的方案:目前已有人制作了 OpenCC 镜像(当然也有 Hugo),更新较为频繁,若可得其助力,倒不失为权宜之计。但是,我不甚理解 Actions 脚本和 Docker 之互动关系:

  • 如果在上述 deploy 任务里指定 container,意味着下面一切指令将在基于该镜像的容器中运行;但当时我理解为简单地引入一个镜像,供一条或多条指令使用,遂未果。
1
2
3
4
5
6
7
deploy:
runs-on: ubuntu-22.04
if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')"
container:
image: peterdavehello/opencc:latest
steps:
...
  • 如果通过一种支持单指令 docker run 的组件(例如 Docker Run Action)来运行 OpenCC,想必是可行的,但我遇到 scripts/opencc.sh: not found 这样的错误,便不知所措了。本质上说,我未经测试便急于将新理念应用到生产环境,理性无能为力,直觉横冲猛撞,结果啼笑皆非。
1
2
3
4
5
6
7
8
- uses: kohlerdominik/docker-run-action@v1
with:
image: peterdavehello/opencc:latest
volumes: ${{ github.workspace }}:/repo
workdir: /repo
shell: /bin/bash
run: |
/repo/scripts/opencc.sh

三、借道 Alpine Linux 隔山打牛

翌日,我在 GitHub 提供的 Ubuntu 镜像资料中发现可通过 Docker 引入 Alpine Linux。此物之轻盈和 Arch 不相上下,其中 Docker 镜像仅占地数十个 MB,用它便可自由引入 Alpine 软件库里的各式软件;而且凑巧的是,不仅有最新版 OpenCC,诸如 Hugo、Node.js 等一系列工具也持续维持最新。如此便可跳出 Ubuntu 的软件版本陷阱,是理想之选。经同事提醒,我利用家里服务器作为宿主,启动 Alpine 容器,进入其终端。
自行测试下来,果然纠正了多处问题:

  • 前述 scripts/opencc.sh: not found 是出于 bash 缺失的缘故;该脚本原先可直接在宿主系统中执行,但我忽视了 Ubuntu 所用终端正是 Bash 的事实。后来以 sh ./scripts/opencc.sh 强制运行后提示语法错误时,我幡然醒悟:终端这东西的确不能混用。
  • OpenCC 以及 Hugo 并未被收入官方分支,反而在 edge 分支中能够找到。故准备环境时需额外引入 edge/community 软件库。
  • Hugo 在构建网页时是依赖 git 的;未安装 Git 的情况下,尽管网页照旧生成,但有报错,且构建后无页面统计。准备环境时需一并引入。

这样我便胸有成竹了。如果单纯依靠指令完成构建,需要以下指令:

1
2
3
4
5
6
7
8
9
10
11
12
# 装载 edge/community 仓库
echo "https://dl-cdn.alpinelinux.org/alpine/edge/community" >> /etc/apk/repositories
apk update

# 准备工具包
apk add bash git hugo nodejs npm opencc
npm ci

# 构建网站
bash ./scripts/opencc.sh
export HUGO_DISABLELANGUAGES='en'
hugo --minify

编辑脚本时,我又心血来潮,干脆不碰那捉摸不透的 Docker,去寻觅同样提供 Alpine 环境的组件;找来了名曰“Setup Alpine Linux environment”的组件,从介绍来看,其性能和易用性显然更胜一筹(毕竟趁手的工具才是好工具)。最终,下面的脚本运行成功!

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
35
36
jobs:
deploy:
runs-on: ubuntu-latest
if: "! contains(toJSON(github.event.commits.*.message), '[skip ci]')"
steps:
- uses: actions/checkout@v3
- name: Prepare Alpine Linux with tools (latest)
uses: jirutka/setup-alpine@v1
with:
extra-repositories: |
https://dl-cdn.alpinelinux.org/alpine/edge/community
packages: >
bash
git
hugo
nodejs
npm
opencc
- name: Execute OpenCC in Alpine
run: bash ./scripts/opencc.sh
shell: alpine.sh {0}
- name: Prepare Node.js packages in Alpine
run: npm ci
shell: alpine.sh {0}
- name: Build the site in Alpine
run: hugo --minify
env:
HUGO_DISABLELANGUAGES: 'en'
shell: alpine.sh {0}
- name: Upload to CloudFlare
run: npx wrangler pages deploy public --project-name tfsci --branch main
if: github.ref == 'refs/heads/main'
env:
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
shell: bash

经验证,家中服务器和生产环境的转换结果得到修复,且完全一致。

四、谈些题外话

其实写了早期脚本的同事和试图改进脚本的,不是同一人。前者基本淡出了网站的维护工作,以至于后者要重构网站时不得不招募其他人员以协助;我本是在技术透明的环境里怡然自得的,而今似乎要重拾一些记忆深处的东西——迄今我还敬而远之。当然,在此次事件中,我仅仅是在搬运积木,并有序组装在一起,这并不触犯记忆“禁区”。

另外,毫无疑问,我更多地展示了感性一面。在观看电视剧《问心》时,生离死别总让我潸然泪下——须知上次催泪的剧集还是去年底的《苍兰诀》……当然以我对中枢神经系统的理解,二十多年养成的理性主导不会为感性主导所取代,但这俩不时会交替上位;出于阿斯伯格存在的缘故,感性上位的机会不多,而理性维持过久也会过载。总之,考虑到感性曾缺席多年,这或许利大于弊罢。

但愿本文对需要 GitHub Actions 部署些东西的读者有所裨益。