跳转到内容

CI/CD

2 篇包含标签 "CI/CD" 的文章

如何用 GitHub Actions 实现 Steam 自动化发布

如何用 GitHub Actions 实现 Steam 自动化发布

Section titled “如何用 GitHub Actions 实现 Steam 自动化发布”

本文分享了 HagiCode Desktop 项目中实现 Steam 自动化发布的完整方案,从 GitHub Release 到 Steam 平台的全链路自动化流程,包括 Steam Guard 认证、多平台 Depot 上传等关键技术细节。

Steam 平台的发布流程,其实和传统的应用分发方式挺不一样的。Steam 有自己的一套完整更新分发系统,开发者得通过 SteamCMD 工具把构建产物上传到 Steam 的 CDN 网络,而不是像其他平台那样直接丢个下载链接就完事了。

HagiCode Desktop 项目计划上架 Steam 平台,这也算是给我们的发布流程带来了点新挑战:

  1. 需要把现有的构建产物转换成 Steam 兼容的格式
  2. 得通过 SteamCMD 工具上传到 Steam 平台
  3. 还必须处理 Steam Guard 认证这玩意儿
  4. 需要支持多平台(Linux、Windows、macOS)的 Depot 上传
  5. 还要实现从 GitHub Release 到 Steam 的自动化流转

项目此前已经实现了”便携版模式”(portable version mode),允许应用检测打包在 extra 目录中的固定服务载荷。我们的目标,其实就是让这套便携版模式和 Steam 分发能无缝集成罢了。

本文分享的方案,来自我们在 HagiCode 项目中的实践经验。HagiCode 是一个 AI 代码助手项目,支持桌面端运行,我们正在推进 Steam 平台的上架工作,因此才需要建立一套可靠的自动化发布流程。

整个 Steam 发布流程的核心是一个 GitHub Actions 工作流,它把整个过程分为三个主要阶段:

┌─────────────────────────────────────────────────────────────┐
│ GitHub Actions Workflow (Steam Release) │
├─────────────────────────────────────────────────────────────┤
│ 1. 准备阶段: │
│ - 检出 portable-version 代码 │
│ - 从 GitHub Release 下载构建产物 │
│ - 解压并准备 Steam 内容目录 │
│ │
│ 2. SteamCMD 设置: │
│ - 安装/复用 SteamCMD │
│ - 使用 Steam Guard 进行认证 │
│ │
│ 3. 发布阶段: │
│ - 生成 Depot VDF 配置文件 │
│ - 生成 App Build VDF 配置文件 │
│ - 调用 SteamCMD 上传到 Steam │
└─────────────────────────────────────────────────────────────┘

这种设计的优势,怎么说呢:

  • 复用现有的 GitHub Release 产物,避免重复构建,毕竟谁愿意重复劳动呢
  • 通过自托管运行器实现安全隔离,多一层保障总是好的
  • 支持预览模式和正式发布分支切换,灵活一点
  • 完整的错误处理和日志记录,出问题的时候不至于太迷茫

我们的工作流支持以下关键参数:

inputs:
release: # Portable Version 发布标签
description: '要发布的版本标签(如 v1.0.0)'
required: true
steam_preview: # 是否生成预览构建
description: '是否为预览模式'
required: false
default: 'false'
steam_branch: # 设置为 live 的 Steam 分支
description: '目标 Steam 分支'
required: false
default: 'preview'
steam_description: # 构建描述覆盖
description: '构建描述'
required: false

出于安全考虑,我们使用带 steam 标签的自托管运行器:

runs-on:
- self-hosted
- Linux
- X64
- steam

这样可以确保 Steam 发布在专用运行器上执行,保持敏感凭据的安全隔离。毕竟安全这事儿,多注意一点总是好的。

为了避免同一版本的发布相互干扰,我们配置了并发控制:

concurrency:
group: portable-version-steam-${{ github.event.inputs.release }}
cancel-in-progress: false

注意这里设置 cancel-in-progress: false,因为 Steam 发布过程可能较长,我们也不想因为新的触发就取消正在进行的发布。毕竟发布个版本也不容易,总得让人家跑完不是?

prepare-steam-release-input.mjs 脚本负责准备发布所需的输入:

// 下载 GitHub Release 的构建清单和产物清单
const buildManifest = await downloadBuildManifest(releaseTag);
const artifactInventory = await downloadArtifactInventory(releaseTag);
// 下载各平台的压缩包
for (const platform of ['linux-x64', 'win-x64', 'osx-universal']) {
const artifactUrl = getArtifactUrl(artifactInventory, platform);
await downloadArtifact(artifactUrl, platform);
}
// 解压到 Steam 内容目录结构
await extractToSteamContent(sources, contentRoot);

Steam 要求使用 Steam Guard 保护账户,我们实现了基于共享密钥的代码生成算法:

function generateSteamGuardCode(sharedSecret, timestamp = Date.now()) {
const secret = decodeSharedSecret(sharedSecret);
const time = Math.floor(timestamp / 1000 / 30);
const timeBuffer = Buffer.alloc(8);
timeBuffer.writeBigUInt64BE(BigInt(time));
// 使用 HMAC-SHA1 生成时间基础的一次性代码
const hash = crypto.createHmac('sha1', secret)
.update(timeBuffer)
.digest();
// 转换为 5 字符的 Steam Guard 代码
const code = steamGuardCode(hash);
return code;
}

这个实现基于 Steam Guard 的 TOTP(Time-based One-Time Password)机制,每 30 秒生成一个新的验证码。毕竟安全这东西,还是得用靠谱的方式才行。

VDF(Valve Data Format)是 Steam 使用的配置格式,我们需要生成两种类型的 VDF 文件:

Depot VDF 用于配置各个平台的内容:

function buildDepotVdf(depotId, contentRoot) {
return [
'"DepotBuildConfig"',
'{',
` "DepotID" "${escapeVdf(depotId)}"`,
` "ContentRoot" "${escapeVdf(contentRoot)}"`,
' "FileMapping"',
' {',
' "LocalPath" "*"',
' "DepotPath" "."',
' "recursive" "1"',
' }',
'}'
].join('\n');
}

App Build VDF 用于配置整个应用构建:

function buildAppBuildVdf(appId, depotBuilds, description, setLive) {
const vdf = [
'"appbuild"',
'{',
` "appid" "${appId}"`,
` "desc" "${escapeVdf(description)}"`,
` "contentroot" "${escapeVdf(contentRoot)}"`,
' "buildoutput" "build_output"',
' "depots"',
' {'
];
for (const [depotId, depotVdfPath] of Object.entries(depotBuilds)) {
vdf.push(` "${depotId}" "${depotVdfPath}"`);
}
if (setLive) {
vdf.push(` }`);
vdf.push(` "setlive" "${setLive}"`);
}
vdf.push('}');
return vdf.join('\n');
}

最后,通过调用 SteamCMD 执行上传:

await runCommand(steamcmdPath, [
'+login', steamUsername, steamPassword, steamGuardCode,
'+run_app_build', appBuildPath,
'+quit'
]);

这一步算是整个流程的最后一跃,跨过去就完成了…

Steam 使用 Depot 系统管理不同平台的内容,我们支持三种主要的 Depot:

平台Depot 标识架构支持
Linuxlinux-x64x64_64
Windowswin-x64x64_64
macOSosx-universaluniversal, x64_64, arm64

每个 Depot 都有独立的内容目录和 VDF 配置文件,这样可以确保不同平台的用户只下载自己需要的内容。毕竟流量也是钱,能省一点是一点。

首先需要在 portable-version 仓库创建一个 GitHub Release,包含:

  • 各平台的压缩包
  • 构建清单({tag}.build-manifest.json
  • 产物清单({tag}.artifact-inventory.json

通过 GitHub Actions 手动触发工作流,填写必要参数:

  • release: 要发布的版本标签(如 v1.0.0)
  • steam_branch: 目标分支(如 previewpublic
  • steam_preview: 是否预览模式

工作流会自动执行以下步骤:

  1. 下载并解压 GitHub Release 产物
  2. 安装/更新 SteamCMD
  3. 生成 Steam VDF 配置文件
  4. 使用 Steam Guard 认证
  5. 上传内容到 Steam CDN
  6. 设置指定分支为 live

这一套流程走下来,也算是把该做的都做了。

在 GitHub 仓库设置中配置以下密钥:

Secret 名称说明
STEAM_USERNAMESteam 账户用户名
STEAM_PASSWORDSteam 账户密码
STEAM_SHARED_SECRETSteam Guard 共享密钥(可选)
STEAM_GUARD_CODESteam Guard 代码(可选)
STEAM_APP_IDSteam 应用 ID
STEAM_DEPOT_ID_LINUXLinux Depot ID
STEAM_DEPOT_ID_WINDOWSWindows Depot ID
STEAM_DEPOT_ID_MACOSmacOS Depot ID

这些配置项,其实也没什么特别的,就是该有的都得有罢了。

变量名称说明默认值
PORTABLE_VERSION_STEAMCMD_ROOTSteamCMD 安装目录~/.local/share/portable-version/steamcmd

首次运行需要手动输入 Steam Guard 代码,之后建议配置共享密钥自动生成代码。这样可以避免每次发布都需要手动干预,毕竟谁也不想每次都重复同样的操作。

SteamCMD 会保存登录令牌,后续可以复用。但要注意令牌的有效期,过期后还是得重新认证的,这也没办法。

确保 Steam 内容目录结构正确:

steam-content/
├── linux-x64/ # Linux 平台内容
├── win-x64/ # Windows 平台内容
└── osx-universal/ # macOS 通用二进制内容

每个目录下应该包含对应平台的完整应用文件。这点倒也没什么好说的,该怎么做就怎么做。

预览模式不会设置任何分支为 live,适合测试验证:

if [ "$STEAM_PREVIEW_INPUT" = 'true' ]; then
cmd+=(--preview)
fi

这样可以先上传到 Steam 平台进行验证,确认无误后再切换到正式分支。多一层验证,总是好的。

脚本包含了完善的错误处理和日志记录:

  • 验证 GitHub Release 存在性
  • 检查必需的元数据文件
  • 确保平台内容存在
  • 生成 GitHub Actions 摘要报告

这些信息对于调试和审计都非常有价值,毕竟出问题的时候能有个线索,总比一头雾水要好。

工作流生成两种产物:

  • portable-steam-release-preparation-{tag}: 发布准备元数据
  • portable-steam-build-metadata-{tag}: Steam 构建元数据

这些产物可以用于后续的审计和调试,保存时间建议设置为 30 天。反正也不占多少地方,留着也无妨。

在 HagiCode 项目中,这套自动化发布流程已经成功运行了多个版本。从 GitHub Release 到 Steam 平台的整个链路完全自动化,无需人工干预。

这大大提高了我们的发布效率和可靠性。之前手动发布一个版本需要 30 分钟以上的时间,现在只需要几分钟就能完成整个流程。时间这东西,省下来总归是好的。

更重要的是,自动化流程减少了人为错误的可能性,每次发布都是标准化的流程,结果也更加可预测。毕竟重复的事情交给机器去做,人也轻松点。

通过本文分享的方案,我们实现了:

  1. 从 GitHub Release 到 Steam 平台的完全自动化
  2. 支持多平台的 Depot 上传
  3. 基于 Steam Guard 的安全认证
  4. 预览模式和正式发布的灵活切换
  5. 完善的错误处理和日志记录

这套方案不仅适用于 HagiCode 项目,也可以为其他计划上架 Steam 平台的项目提供参考。如果你也在考虑 Steam 自动化发布,希望本文的实践能够对你有所帮助。

其实技术这东西,说复杂也复杂,说简单也简单。关键是找到适合自己的方式罢了。

如果本文对你有帮助,欢迎来 HagiCode 的 GitHub 仓库给个 Star,或者访问官网了解更多信息。

感谢您的阅读,如果您觉得本文有用,欢迎点赞、收藏和分享支持。 本内容采用人工智能辅助协作,最终内容由作者审核并确认。

HagiCode 实践:如何利用 GitHub Actions 实现 Docusaurus 自动部署

为 HagiCode 添加 GitHub Pages 自动部署支持

Section titled “为 HagiCode 添加 GitHub Pages 自动部署支持”

本项目早期代号为 PCode,现已正式更名为 HagiCode。本文记录了如何为项目引入自动化静态站点部署能力,让内容发布像喝水一样简单。

在 HagiCode 的开发过程中,我们遇到了一个很现实的问题:随着文档和提案越来越多,如何高效地管理和展示这些内容成了当务之急。我们决定引入 GitHub Pages 来托管我们的静态站点,但是手动构建和部署实在是太麻烦了——每次改动都要本地构建、打包,然后手动推送到 gh-pages 分支。这不仅效率低下,还容易出错。

为了解决这个问题(主要是为了偷懒),我们需要一套自动化的部署流程。本文将详细记录如何为 HagiCode 项目添加 GitHub Actions 自动部署支持,让我们只需专注于内容创作,剩下的交给自动化流程。

嘿,介绍一下我们正在做的东西

我们正在开发 HagiCode——一款 AI 驱动的代码智能助手,让开发体验变得更智能、更便捷、更有趣。

智能——AI 全程辅助,从想法到代码,让编码效率提升数倍。便捷——多线程并发操作,充分利用资源,开发流程顺畅无阻。有趣——游戏化机制和成就系统,让编码不再枯燥,充满成就感。

项目正在快速迭代中,如果你对技术写作、知识管理或者 AI 辅助开发感兴趣,欢迎来 GitHub 看看~

在动手之前,我们得先明确这次任务到底要干啥。毕竟(这里打错了,应该是毕竟)磨刀不误砍柴工嘛。

  1. 自动化构建:当代码推送到 main 分支时,自动触发构建流程。
  2. 自动部署:构建成功后,自动将生成的静态文件部署到 GitHub Pages。
  3. 环境一致性:确保 CI 环境和本地构建环境一致,避免”本地能跑,线上报错”的尴尬。

考虑到 HagiCode 是基于 Docusaurus 构建的(一种非常流行的 React 静态站点生成器),我们可以利用 GitHub Actions 来实现这一目标。

GitHub Actions 是 GitHub 提供的 CI/CD 服务。通过在代码仓库中定义 YAML 格式的工作流文件,我们可以定制各种自动化任务。

我们需要在项目根目录下的 .github/workflows 文件夹中创建一个新的配置文件,比如叫 deploy.yml。如果文件夹不存在,记得先手动创建一下。

这个配置文件的核心逻辑如下:

  1. 触发条件:监听 main 分支的 push 事件。
  2. 运行环境:最新版的 Ubuntu。
  3. 构建步骤
    • 检出代码
    • 安装 Node.js
    • 安装依赖 (npm install)
    • 构建静态文件 (npm run build)
  4. 部署步骤:使用官方提供的 action-gh-pages 将构建产物推送到 gh-pages 分支。

以下是我们最终采用的配置模板:

name: Deploy to GitHub Pages
# 触发条件:当推送到 main 分支时
on:
push:
branches:
- main
# 可以根据需要添加路径过滤,比如只有文档变动才构建
# paths:
# - 'docs/**'
# - 'package.json'
# 设置权限,这对于部署到 GitHub Pages 很重要
permissions:
contents: read
pages: write
id-token: write
# 并发控制:取消同一分支的旧构建
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
# 注意:必须设置 fetch-depth: 0,否则可能导致构建版本号不准确
with:
fetch-depth: 0
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20 # 建议与本地开发环境保持一致
cache: 'npm' # 启用缓存可以加速构建过程
- name: Install dependencies
run: npm ci
# 使用 npm ci 而不是 npm install,因为它更快、更严格,适合 CI 环境
- name: Build website
run: npm run build
env:
# 如果你的站点构建需要环境变量,在这里配置
# NODE_ENV: production
# PUBLIC_URL: /your-repo-name
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./build # Docusaurus 默认输出目录
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

在实际操作中,我们遇到了一些问题,这里分享出来希望大家能避开(或者提前准备好解决方案)。

最开始配置的时候,部署总是报错 403 (Forbidden)。查了好久才发现,是因为 GitHub 默认的 GITHUB_TOKEN 并没有写入 Pages 的权限。

解决方案:在仓库的 Settings -> Actions -> General -> Workflow permissions 中,务必选择 “Read and write permissions”

Docusaurus 默认把构建好的静态文件放在 build 目录。但是有些项目(比如 Create React App 默认是 build,Vite 默认是 dist)可能配置不一样。如果在 Actions 中报错找不到文件,记得去 docusaurus.config.js 里检查一下输出路径配置。

如果你的仓库不是用户主页(即不是 username.github.io),而是项目主页(比如 username.github.io/project-name),你需要配置 baseUrl

docusaurus.config.js 中:

module.exports = {
// ...
url: 'https://hagicode.com', // 你的 Hagicode URL
baseUrl: '/', // 根路径部署
// ...
};

这一点很容易被忽略,配置不对会导致页面打开全是白屏,因为资源路径加载不到。

配置完所有东西并推送代码后,我们就可以去 GitHub 仓库的 Actions 标签页看戏了。

你会看到黄色的圆圈(工作流正在运行),变绿就代表成功啦!如果变红了,点击进去查看日志,通常都能排查出问题(大部分时候是拼写错误或者路径配置不对)。

构建成功后,访问 https://<你的用户名>.github.io/<仓库名>/ 就能看到崭新的站点了。

通过引入 GitHub Actions,我们成功实现了 HagiCode 文档站的自动化部署。这不仅节省了手动操作的时间,更重要的是保证了发布流程的标准化。现在不管是哪位小伙伴更新了文档,只要合并到 main 分支,几分钟后就能在线上看到最新的内容。

核心收益

  • 效率提升:从”手动打包、手动上传”变成”代码即发布”。
  • 降低错误:消除了人为操作失误的可能性。
  • 体验优化:让开发者更专注于内容质量,而不是被繁琐的部署流程困扰。

虽然配置 CI/CD 刚开始有点麻烦(尤其是各种权限和路径问题),但这是一次性投入,长期回报巨大的工作。强烈建议所有静态站点项目都接入类似的自动化流程。


感谢您的阅读,如果您觉得本文有用,快点击下方点赞按钮👍,让更多的人看到本文。

本内容采用人工智能辅助协作,经本人审核,符合本人观点与立场。