How to Implement Automated Steam Publishing with GitHub Actions
This article shares a complete solution for implementing automated Steam publishing in the HagiCode Desktop project, covering the full automation pipeline from GitHub Release to the Steam platform, including key technical details such as Steam Guard authentication and multi-platform Depot uploads.
Background
The Steam platform's publishing process is actually quite different from traditional application distribution methods. Steam has its own complete update distribution system. Developers need to upload build artifacts to Steam's CDN network using the SteamCMD tool, rather than just throwing out a download link like other platforms.
The HagiCode Desktop project plans to launch on the Steam platform, which has brought some new challenges to our publishing process:
- Need to convert existing build artifacts into Steam-compatible format
- Must upload to Steam platform via SteamCMD tool
- Must handle Steam Guard authentication
- Need to support multi-platform (Linux, Windows, macOS) Depot uploads
- Need to implement automated flow from GitHub Release to Steam
The project had previously implemented a "portable version mode" that allows the application to detect fixed service payloads packaged in the extra directory. Our goal is to seamlessly integrate this portable version mode with Steam distribution.
About HagiCode
The solution shared in this article comes from our practical experience in the HagiCode project. HagiCode is an AI code assistant project that supports desktop execution. We are working on launching on the Steam platform, which is why we needed to establish a reliable automated publishing process.
Architecture Design
The core of the entire Steam publishing process is a GitHub Actions workflow that divides the process into three main stages:
┌─────────────────────────────────────────────────────────────┐
│ GitHub Actions Workflow (Steam Release) │
├─────────────────────────────────────────────────────────────┤
│ 1. Preparation Phase: │
│ - Checkout portable-version code │
│ - Download build artifacts from GitHub Release │
│ - Extract and prepare Steam content directory │
│ │
│ 2. SteamCMD Setup: │
│ - Install/reuse SteamCMD │
│ - Authenticate using Steam Guard │
│ │
│ 3. Publishing Phase: │
│ - Generate Depot VDF configuration files │
│ - Generate App Build VDF configuration files │
│ - Call SteamCMD to upload to Steam │
└─────────────────────────────────────────────────────────────┘
The advantages of this design are:
- Reuses existing GitHub Release artifacts, avoiding duplicate builds
- Achieves security isolation through self-hosted runners
- Supports preview mode and formal release branch switching
- Complete error handling and logging
Workflow Implementation
Trigger Parameter Design
Our workflow supports the following key parameters:
inputs:
release: # Portable Version release tag
description: 'Version tag to publish (e.g., v1.0.0)'
required: true
steam_preview: # Whether to generate preview build
description: 'Whether to enable preview mode'
required: false
default: 'false'
steam_branch: # Steam branch to set to live
description: 'Target Steam branch'
required: false
default: 'preview'
steam_description: # Build description override
description: 'Build description'
required: false
Self-Hosted Runner Configuration
For security reasons, we use a self-hosted runner with the steam label:
runs-on:
- self-hosted
- Linux
- X64
- steam
This ensures that Steam publishing is executed on a dedicated runner, maintaining secure isolation of sensitive credentials.
Concurrency Control
To prevent releases of the same version from interfering with each other, we configured concurrency control:
concurrency:
group: portable-version-steam-${{ github.event.inputs.release }}
cancel-in-progress: false
Note that cancel-in-progress: false is set here because the Steam publishing process can be lengthy, and we don't want to cancel an ongoing release due to a new trigger.
Core Script Implementation
Preparing Release Input
The prepare-steam-release-input.mjs script is responsible for preparing the input needed for publishing:
// Download build manifest and artifact inventory from GitHub Release
const buildManifest = await downloadBuildManifest(releaseTag);
const artifactInventory = await downloadArtifactInventory(releaseTag);
// Download compressed packages for each platform
for (const platform of ['linux-x64', 'win-x64', 'osx-universal']) {
const artifactUrl = getArtifactUrl(artifactInventory, platform);
await downloadArtifact(artifactUrl, platform);
}
// Extract to Steam content directory structure
await extractToSteamContent(sources, contentRoot);
Steam Guard Authentication
Steam requires using Steam Guard to protect accounts. We implemented a code generation algorithm based on shared secrets:
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));
// Use HMAC-SHA1 to generate time-based one-time code
const hash = crypto.createHmac('sha1', secret)
.update(timeBuffer)
.digest();
// Convert to 5-character Steam Guard code
const code = steamGuardCode(hash);
return code;
}
This implementation is based on Steam Guard's TOTP (Time-based One-Time Password) mechanism, generating a new verification code every 30 seconds.
VDF Configuration Generation
VDF (Valve Data Format) is the configuration format used by Steam. We need to generate two types of VDF files:
Depot VDF is used to configure content for each platform:
function buildDepotVdf(depotId, contentRoot) {
return [
'"DepotBuildConfig"',
'{',
` "DepotID" "${escapeVdf(depotId)}"`,
` "ContentRoot" "${escapeVdf(contentRoot)}"`,
' "FileMapping"',
' {',
' "LocalPath" "*"',
' "DepotPath" "."',
' "recursive" "1"',
' }',
'}'
].join('\n');
}
App Build VDF is used to configure the entire application build:
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 Invocation
Finally, upload is performed by calling SteamCMD:
await runCommand(steamcmdPath, [
'+login', steamUsername, steamPassword, steamGuardCode,
'+run_app_build', appBuildPath,
'+quit'
]);
This step is the final leap of the entire process.
Multi-Platform Depot Handling
Steam uses the Depot system to manage content for different platforms. We support three main Depots:
| Platform | Depot Identifier | Architecture Support |
|---|---|---|
| Linux | linux-x64 |
x64_64 |
| Windows | win-x64 |
x64_64 |
| macOS | osx-universal |
universal, x64_64, arm64 |
Each Depot has an independent content directory and VDF configuration file, ensuring that users on different platforms only download the content they need.
Publishing Process
Step 1: Prepare GitHub Release
First, you need to create a GitHub Release in the portable-version repository, including:
- Compressed packages for each platform
- Build manifest (
{tag}.build-manifest.json) - Artifact inventory (
{tag}.artifact-inventory.json)
Step 2: Trigger Steam Publishing Workflow
Manually trigger the workflow through GitHub Actions and fill in the necessary parameters:
-
release: Version tag to publish (e.g., v1.0.0) -
steam_branch: Target branch (e.g.,previeworpublic) -
steam_preview: Whether to enable preview mode
Step 3: Automatic Publishing Process
The workflow will automatically execute the following steps:
- Download and extract GitHub Release artifacts
- Install/update SteamCMD
- Generate Steam VDF configuration files
- Authenticate using Steam Guard
- Upload content to Steam CDN
- Set specified branch to live
Configuration Guide
Required Secrets Configuration
Configure the following secrets in GitHub repository settings:
| Secret Name | Description |
|---|---|
STEAM_USERNAME |
Steam account username |
STEAM_PASSWORD |
Steam account password |
STEAM_SHARED_SECRET |
Steam Guard shared secret (optional) |
STEAM_GUARD_CODE |
Steam Guard code (optional) |
STEAM_APP_ID |
Steam application ID |
STEAM_DEPOT_ID_LINUX |
Linux Depot ID |
STEAM_DEPOT_ID_WINDOWS |
Windows Depot ID |
STEAM_DEPOT_ID_MACOS |
macOS Depot ID |
Environment Variable Configuration
| Variable Name | Description | Default Value |
|---|---|---|
PORTABLE_VERSION_STEAMCMD_ROOT |
SteamCMD installation directory | ~/.local/share/portable-version/steamcmd |
Best Practices
Steam Guard Authentication Management
First-time run requires manually entering the Steam Guard code. After that, it's recommended to configure a shared secret for automatic code generation. This avoids the need for manual intervention with each publish.
SteamCMD will save the login token for subsequent reuse. However, note the token's validity period - it will need re-authentication after expiration.
Content Directory Structure
Ensure the Steam content directory structure is correct:
steam-content/
├── linux-x64/ # Linux platform content
├── win-x64/ # Windows platform content
└── osx-universal/ # macOS universal binary content
Each directory should contain the complete application files for the corresponding platform.
Using Preview Mode
Preview mode does not set any branch to live, making it suitable for testing and verification:
if [ "$STEAM_PREVIEW_INPUT" = 'true' ]; then
cmd+=(--preview)
fi
This allows uploading to the Steam platform for verification first, then switching to the formal branch after confirmation.
Error Handling and Logging
The script includes comprehensive error handling and logging:
- Verify GitHub Release existence
- Check required metadata files
- Ensure platform content exists
- Generate GitHub Actions summary reports
This information is very valuable for debugging and auditing.
Artifact Management
The workflow generates two types of artifacts:
-
portable-steam-release-preparation-{tag}: Publishing preparation metadata -
portable-steam-build-metadata-{tag}: Steam build metadata
These artifacts can be used for subsequent auditing and debugging. It's recommended to set the retention time to 30 days.
Practical Application
In the HagiCode project, this automated publishing process has successfully run for multiple versions. The entire pipeline from GitHub Release to Steam platform is fully automated without manual intervention.
This has significantly improved our publishing efficiency and reliability. Previously, manually publishing a version took over 30 minutes, but now the entire process can be completed in just a few minutes.
More importantly, the automated process reduces the possibility of human error. Each publish follows a standardized process with more predictable results.
Summary
Through the solution shared in this article, we have achieved:
- Full automation from GitHub Release to Steam platform
- Support for multi-platform Depot uploads
- Security authentication based on Steam Guard
- Flexible switching between preview mode and formal publishing
- Comprehensive error handling and logging
This solution is not only applicable to the HagiCode project but can also provide reference for other projects planning to launch on the Steam platform. If you're also considering Steam automated publishing, I hope the practices shared in this article can be helpful to you.
If this article helps you, feel free to give a Star on HagiCode's GitHub repository or visit the official website for more information.
References
- SteamCMD Documentation
- Steamworks SDK
- HagiCode Project Repository
- HagiCode Official Website
- HagiCode Installation Guide
- HagiCode Desktop
Original Article & License
Thanks for reading. If this article helped, consider liking, bookmarking, or sharing it.
This article was created with AI assistance and reviewed by the author before publication.
- Author: newbe36524
- Original URL: https://docs.hagicode.com/go?platform=devto&target=%2Fblog%2F2026-04-16-steam-release-automation-github-actions%2F
- License: Unless otherwise stated, this article is licensed under CC BY-NC-SA. Please retain attribution when sharing.
Top comments (0)