惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

IT之家
IT之家
L
LangChain Blog
博客园 - 聂微东
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
GbyAI
GbyAI
博客园_首页
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
酷 壳 – CoolShell
酷 壳 – CoolShell
D
Docker
V
V2EX
月光博客
月光博客
Recent Commits to openclaw:main
Recent Commits to openclaw:main
S
SegmentFault 最新的问题
雷峰网
雷峰网
Stack Overflow Blog
Stack Overflow Blog
Cyberwarzone
Cyberwarzone
P
Privacy International News Feed
Spread Privacy
Spread Privacy
Project Zero
Project Zero
腾讯CDC
Engineering at Meta
Engineering at Meta
T
Tenable Blog
aimingoo的专栏
aimingoo的专栏
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Jina AI
Jina AI
大猫的无限游戏
大猫的无限游戏
量子位
Blog — PlanetScale
Blog — PlanetScale
D
DataBreaches.Net
T
Troy Hunt's Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
美团技术团队
N
News and Events Feed by Topic
T
Tor Project blog
H
Help Net Security
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Last Week in AI
Last Week in AI
S
Security Affairs
小众软件
小众软件
Scott Helme
Scott Helme
云风的 BLOG
云风的 BLOG
Apple Machine Learning Research
Apple Machine Learning Research
Vercel News
Vercel News
人人都是产品经理
人人都是产品经理
PCI Perspectives
PCI Perspectives
TaoSecurity Blog
TaoSecurity Blog
博客园 - 叶小钗
V
Visual Studio Blog
The Cloudflare Blog
Recent Announcements
Recent Announcements

博客园_首页

Plist 二进制格式 Milvus 和 PGVector,哪个更好? OpenClaw 已过时?在 VS Code 中运行 Hermes Agent! 分享一下笔者的 Mac 装机必备软件 第30篇文章:一个大三计科生的自白 Manim如何在数学公式中完美显示中文? Docker 部署 RocketMQ 5 并发编程核心概念辨析 C#事务处理最佳实践:别再让“主表存了、明细丢了”的破事发生 CLI 是什么?为什么大厂突然集体卷命令行? 【从0到1构建一个ClaudeAgent】协作-自主Agent UIImageView 设置图片不生效的原因排查 最小二乘问题详解20:无先验约束下的增量式SFM自由网平差 痞子衡嵌入式:大话双核i.MXRT1180之XIP应用里借助MU实现可靠Flash IAP的方法 AI Chat 封装, SemanticKerne.AiProvider.Unified 已发布 Windows下右键编辑js文件无法打开记事本——在注册表中使用环境变量 在后台服务中使用 Scoped 服务,为什么总是报错? H200 安装驱动并使用sglang启动模型 wireshark 抓包Trap上报告警内容 我用 AI 辅助开发了一系列小工具(2):图片压缩工具 [A Primer On MC and CC] 2.1 Memory Consistency 1 - 指令重排序和 SC 模型 Oracle数据库SCN推进技术详解与实践指南 玩转控件:封装个带图片的Label控件 Claude Code 4.7 真正该升级的不是模型,而是你的工作流 前端小白一句话,AI 帮我做了个颜值拉满的桌面媒体播放器。当代码不再是门槛,一句话编程就是现实。 5. WorkBuddy: 小龙虾的灵魂三件套,让你的小龙虾不只是工具 SQLite 分片方案实战:三种分片策略的深度对比 告别简陋 UI!一款基于 Fluent Design 和基于 WinUI 的开源免费、现代化的 Avalonia UI 控件库 关于二进制排列组合枚举的总结 AI开发-python-LangGraph框架(3-27-LangGraph从零实现大模型智能决策工作流) ElasticSearch主分片和副本分片概念详解 【002】HTTPS 粗解:证书、TLS 握手与对后端配置的影响 Hermes Agent 一周暴涨五万 Star,但我劝你别急着追 明明连接的是Redis的DB0,为什么能查到DB3的数据? 【从0到1构建一个ClaudeAgent】协作-Agent团队 熟悉电子元器件之后,电子小白下一步该怎么走? MAF快速入门(23)通过C#类定义Skills .NET 高级开发 | 手写一个对象映射框架 FastAPI数据库ORM怎么选?我肝了三个Demo后,终于不再纠结了 mysqldump 参数拾遗:在遗忘与铭记之间 C# .NET 周刊|2026年3月5期 Claude code入门 - 陈彦斌 一文学习入门 ThingsBoard 开源物联网平台 GitHub 热门项目 | 2026年04月16日 如何为GIT设置全局勾子,为每次提交追加信息 Number.isFinite和isFinite与isNaN()和Number.isNaN的区别 PortSwigger SQL注入LAB2 推荐一个测试人必备的Skills,从功能到性能全搞定(附详细实操和安装下载方式) 筑基期:掌握Odoo基础核心知识点02(Odoo XML 开发方式详解) GLM模型这么火,咱们用vllm也咧一个呗! 深入理解 AbortController:从底层原理到跨语言设计哲学 字符串学习笔记 多租户系统框架的基础模块设计和分析设计 Apache SeaTunnel Zeta 为什么能做到“又快又稳”? AI开发-python-LangGraph框架(3-26-LangGraph基本概念及第一个简单样例) Vue 3 组件通信,别只会用 Props 和 Emits 了,这几个狠活儿你得看看 ElasticSearch7.X版本配置密码 用Manim实现动态交点计算--从一个动点问题说起 团结引擎+Addressable+Instant Game打包抖音小游戏 function call 实战:让 LLM 自动判断 pod 异常、调用日志工具并完成故障分析 bubseek —— 让 Agent 的足迹,变成团队的洞察 通过 C# 读取并导出 PDF 书签 【从0到1构建一个ClaudeAgent】并发-后台任务 .NET 高级开发 | 定制 ASP.NET Core 框架 电子小白:什么是运算放大器(运放) zero2Agent:面向大厂面试的 Agent 工程教程,从概念到生产的完整学习路线 堆上的ORW HC32F460 USB CDC通信异常:非对齐访问异常排查 20260413-Hyperbridge 攻击事件:发生在默克尔山上的验证绕过 那些喊着AI 要淘汰你的人,正在靠你的焦虑赚大钱! 深度学习进阶(八)Swin Transformer 最小二乘问题详解19:带先验约束的增量式SFM优化与实现 SnapTranslate 3.0 正式发布:全局划词翻译 + 完整英语学习闭环,一站式搞定查词、记词、复习 工作的意义、工作的困难认知再思考 .NET + AI 进阶实战:基于类的技能开发 - 打造可治理的 Agent 能力模块 【从0到1构建一个ClaudeAgent】规划与协调-技能 上周热点回顾(4.6-4.12) 电子小白的工具三件套:面包板、杜邦线、万能板 单表五亿数据的查询优化 | Mysql、StarRocks 2. WorkBuddy:从“我是谁”到“帮我干活” C# 如何减少代码运行时间:7 个实战技巧 基于HelixToolkit.SharpDX 渲染3D模型 - 笺上知微 从零开始的双臂具身VLA起源及现阶段发展综述 - SkyXZ 记对 xonsh shell 的使用, 脚本编写, 迁移及调优 - pluvium27 受够了Vibe Coding的失控?换个起点,让AI事半功倍 从开始配置漏洞环境到漏洞复现流程 - 難しい 关于10年工作经验的程序员对OpenClaw的实战经验分享以及看法 - 虚无境 Any metadata 的内存布局 C# .NET 周刊|2026年3月2期 - InCerry 我帮你测过了,测试圈排名第二的 Skill 依然很牛逼 Skill Discovery | 无监督技能发现的经典工作总结 - MoonOut 上下文工程是什么?过时了么?一文讲明白! - 一枫说码 开了 TUN 模式还是直连?90% 的人都踩过这个坑 AScript扩展多种脚本语言 - rockey627 AI 学习笔记:Agent 的记忆机制 你能被装进一个文件里吗?——7 万人把同事"蒸馏"成了 AI - 我没有三颗心脏 Claude Code 通关手册(七):给 AI 装上技能包——Skills 完全指南 - 暮色之狐 在浏览器中快速编辑代码:VSCode Web 集成实践 - Newbe36524 蒸馏自己 skill?基于 Deepseek 的蒸馏器,丐版蒸馏方式,简单便捷 - To_Carpe_Diem Spring AI Aliababa和AgentScope,哪个更好? - 苏三说技术
如何用 GitHub Actions 实现 Steam 自动化发布
Newbe36524 · 2026-04-16 · via 博客园_首页

如何用 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 项目中的实践经验。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 Guard 认证

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 配置生成

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 调用

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

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

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

多平台 Depot 处理

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

平台 Depot 标识 架构支持
Linux linux-x64 x64_64
Windows win-x64 x64_64
macOS osx-universal universal, x64_64, arm64

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

发布流程

步骤 1:准备 GitHub Release

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

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

步骤 2:触发 Steam 发布工作流

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

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

步骤 3:自动执行发布流程

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

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

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

配置指南

必需的 Secrets 配置

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

Secret 名称 说明
STEAM_USERNAME Steam 账户用户名
STEAM_PASSWORD Steam 账户密码
STEAM_SHARED_SECRET Steam Guard 共享密钥(可选)
STEAM_GUARD_CODE Steam Guard 代码(可选)
STEAM_APP_ID Steam 应用 ID
STEAM_DEPOT_ID_LINUX Linux Depot ID
STEAM_DEPOT_ID_WINDOWS Windows Depot ID
STEAM_DEPOT_ID_MACOS macOS Depot ID

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

环境变量配置

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

最佳实践

Steam Guard 认证管理

首次运行需要手动输入 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,或者访问官网了解更多信息。

参考资料

原文与版权说明

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