Skip to content

Fix visual smoke shutdown in CI #23

Fix visual smoke shutdown in CI

Fix visual smoke shutdown in CI #23

Workflow file for this run

name: Release
on:
push:
tags:
- "v*"
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
WINDOWS_TARGET: x86_64-pc-windows-gnu
HIDMAESTRO_RELEASE_URL: https://github.com/hifihedgehog/HIDMaestro/releases/download/v1.3.13/HIDMaestro-v1.3.13.zip
HIDMAESTRO_RELEASE_SHA256: 4948B815E65AFDD72BED8C1E29E8108ED8388B58F805FECA2C452E06CF499227
jobs:
rust-checks:
name: Rust release checks (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: ""
target-arg: ""
- os: windows-latest
target: x86_64-pc-windows-gnu
target-arg: --target x86_64-pc-windows-gnu
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- name: Add Windows Rust target
if: runner.os == 'Windows'
run: rustup target add ${{ matrix.target }}
- uses: Swatinem/rust-cache@v2
- name: Add MinGW to PATH
if: runner.os == 'Windows'
shell: pwsh
run: '"C:\msys64\mingw64\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append'
- name: Install Linux build deps
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y libudev-dev
- name: Format check
run: cargo fmt --all -- --check
- name: Clippy
run: cargo clippy --workspace --all-targets ${{ matrix.target-arg }} -- -D warnings
- name: Test
run: cargo test --workspace ${{ matrix.target-arg }}
web-checks:
name: Web release checks
runs-on: ubuntu-latest
defaults:
run:
working-directory: web
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: "24"
cache: npm
cache-dependency-path: web/package-lock.json
- run: npm ci
- run: npx playwright install --with-deps chromium
- run: npm run typecheck
- run: npm run test:source-audit
- run: npm run test:button-map
- run: npm run build
- run: npm run test:release-size
- run: npm run test:visual-smoke
- uses: actions/upload-artifact@v6
with:
name: release-web-dist
path: web/dist/**
if-no-files-found: error
windows-artifacts:
name: Windows unsigned beta artifacts
runs-on: windows-latest
needs:
- rust-checks
- web-checks
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: actions/setup-dotnet@v5
with:
dotnet-version: "10.0.x"
- uses: actions/download-artifact@v6
with:
name: release-web-dist
path: web/dist
- name: Add MinGW to PATH
shell: pwsh
run: '"C:\msys64\mingw64\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append'
- name: Add Windows Rust target
shell: pwsh
run: rustup target add $env:WINDOWS_TARGET
- name: Resolve release version
id: version
shell: pwsh
run: |
$tag = '${{ github.ref_name }}'
if ($tag -notmatch '^v?([0-9]+\.[0-9]+)(?:\.([0-9]+))?(?:[-+].*)?$') {
throw "Release tag '$tag' must look like v0.3, v0.3.2, or v0.3.2-beta.1."
}
$patch = if ([string]::IsNullOrWhiteSpace($Matches[2])) { '0' } else { $Matches[2] }
"msi_version=$($Matches[1]).$patch" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
"artifact_suffix=$tag" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
- name: Build Windows GNU binaries
shell: pwsh
run: cargo build -p dscc-agent -p dscc-tray -p dscc-cli --release --target $env:WINDOWS_TARGET
- name: Resolve HIDMaestro broker dependency
id: hidmaestro
shell: pwsh
run: |
$root = Join-Path $PWD 'target\hidmaestro'
$zipPath = Join-Path $root 'HIDMaestro-v1.3.13.zip'
$extractPath = Join-Path $root 'release'
New-Item -ItemType Directory -Path $root -Force | Out-Null
Invoke-WebRequest -Uri $env:HIDMAESTRO_RELEASE_URL -OutFile $zipPath
$hash = (Get-FileHash -LiteralPath $zipPath -Algorithm SHA256).Hash.ToUpperInvariant()
if ($hash -ne $env:HIDMAESTRO_RELEASE_SHA256.ToUpperInvariant()) {
throw "HIDMaestro release hash mismatch. Expected $env:HIDMAESTRO_RELEASE_SHA256 but found $hash."
}
if (Test-Path -LiteralPath $extractPath) {
Remove-Item -LiteralPath $extractPath -Recurse -Force
}
Expand-Archive -LiteralPath $zipPath -DestinationPath $extractPath -Force
$coreDll = Join-Path $extractPath 'HIDMaestro.Core.dll'
if (-not (Test-Path -LiteralPath $coreDll)) {
throw "HIDMaestro.Core.dll was not found in the approved release archive."
}
"core_dll=$coreDll" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append
- name: Build HIDMaestro broker package flavors
shell: pwsh
run: |
$coreDll = '${{ steps.hidmaestro.outputs.core_dll }}'
$publishRoot = Join-Path $PWD 'target\hidmaestro-broker'
$selfContained = Join-Path $publishRoot 'self-contained'
$frameworkDependent = Join-Path $publishRoot 'framework-dependent'
foreach ($dir in @($selfContained, $frameworkDependent)) {
if (Test-Path -LiteralPath $dir) {
Remove-Item -LiteralPath $dir -Recurse -Force
}
}
dotnet publish tools/dscc-hidmaestro-broker `
-c Release `
-r win-x64 `
-o $selfContained `
--self-contained true `
-p:PublishSingleFile=true `
-p:EnableCompressionInSingleFile=true `
-p:DebugType=None `
-p:DebugSymbols=false `
-p:HidMaestroCoreDll="$coreDll"
dotnet publish tools/dscc-hidmaestro-broker `
-c Release `
-r win-x64 `
-o $frameworkDependent `
--self-contained false `
-p:PublishSingleFile=false `
-p:DebugType=None `
-p:DebugSymbols=false `
-p:HidMaestroCoreDll="$coreDll"
- name: Build unsigned MSIs
shell: pwsh
run: |
$selfContainedBroker = Join-Path $PWD 'target\hidmaestro-broker\self-contained'
$frameworkDependentBroker = Join-Path $PWD 'target\hidmaestro-broker\framework-dependent'
powershell -NoProfile -ExecutionPolicy Bypass -File packaging\package-msi.ps1 `
-Version '${{ steps.version.outputs.msi_version }}' `
-TargetTriple $env:WINDOWS_TARGET `
-SkipWebBuild `
-InstallerFlavor Standard
powershell -NoProfile -ExecutionPolicy Bypass -File packaging\package-msi.ps1 `
-Version '${{ steps.version.outputs.msi_version }}' `
-TargetTriple $env:WINDOWS_TARGET `
-SkipWebBuild `
-InstallerFlavor Bridge `
-BrokerPublishPath $selfContainedBroker
powershell -NoProfile -ExecutionPolicy Bypass -File packaging\package-msi.ps1 `
-Version '${{ steps.version.outputs.msi_version }}' `
-TargetTriple $env:WINDOWS_TARGET `
-SkipWebBuild `
-InstallerFlavor BridgeFrameworkDependent `
-BrokerPublishPath $frameworkDependentBroker
- name: Stage Windows artifacts and checksums
shell: pwsh
run: |
$suffix = '${{ steps.version.outputs.artifact_suffix }}'
$artifactRoot = Join-Path $PWD 'target\release-artifacts\windows'
New-Item -ItemType Directory -Path $artifactRoot -Force | Out-Null
$msiArtifacts = @(
@{ Flavor = 'standard'; Name = 'standard' },
@{ Flavor = 'bridge'; Name = 'bridge' },
@{ Flavor = 'bridge-framework-dependent'; Name = 'bridge-framework-dependent' }
)
foreach ($artifact in $msiArtifacts) {
$msiSource = Join-Path $PWD "target\installer\DualSenseCommandCenter-${{ steps.version.outputs.msi_version }}-$($artifact.Flavor).msi"
$msiDest = Join-Path $artifactRoot "DualSenseCommandCenter-$suffix-windows-x86_64-$($artifact.Name)-unsigned.msi"
Copy-Item -LiteralPath $msiSource -Destination $msiDest -Force
}
$binaryStage = Join-Path $PWD 'target\release-artifacts\windows-bin'
if (Test-Path -LiteralPath $binaryStage) {
Remove-Item -LiteralPath $binaryStage -Recurse -Force
}
New-Item -ItemType Directory -Path $binaryStage -Force | Out-Null
Copy-Item -LiteralPath "target\$env:WINDOWS_TARGET\release\dscc-agent.exe" -Destination $binaryStage -Force
Copy-Item -LiteralPath "target\$env:WINDOWS_TARGET\release\dscc-tray.exe" -Destination $binaryStage -Force
Copy-Item -LiteralPath "target\$env:WINDOWS_TARGET\release\dscc-cli.exe" -Destination $binaryStage -Force
Set-Content -LiteralPath (Join-Path $binaryStage 'README.txt') -Encoding ASCII -Value @(
'DualSense Command Center unsigned Windows beta binaries.'
'Install the MSI for normal testing; these raw binaries are provided for diagnostics.'
'Use dscc-cli.exe support-bundle if the UI will not open and you need a sanitized report.'
)
$zipDest = Join-Path $artifactRoot "DualSenseCommandCenter-$suffix-windows-x86_64-binaries.zip"
Compress-Archive -Path (Join-Path $binaryStage '*') -DestinationPath $zipDest -Force
$checksumPath = Join-Path $artifactRoot 'SHA256SUMS-windows.txt'
Get-ChildItem -LiteralPath $artifactRoot -File |
Sort-Object Name |
ForEach-Object {
$hash = (Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256).Hash.ToLowerInvariant()
"$hash $($_.Name)"
} |
Set-Content -LiteralPath $checksumPath -Encoding ASCII
- uses: actions/upload-artifact@v6
with:
name: windows-unsigned-beta
path: target/release-artifacts/windows/*
if-no-files-found: error
linux-artifacts:
name: Linux bundled beta artifact
runs-on: ubuntu-latest
needs:
- rust-checks
- web-checks
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: actions/download-artifact@v6
with:
name: release-web-dist
path: web/dist
- name: Install Linux build deps
run: sudo apt-get update && sudo apt-get install -y libudev-dev
- name: Build Linux binaries
run: cargo build -p dscc-agent -p dscc-cli --release
- name: Stage Linux artifacts and checksums
shell: bash
run: |
set -euo pipefail
tag="${GITHUB_REF_NAME}"
artifact_root="$PWD/target/release-artifacts/linux"
stage="$PWD/target/release-artifacts/linux-bin"
rm -rf "$artifact_root" "$stage"
mkdir -p "$artifact_root" "$stage"
cp target/release/dscc-agent "$stage/"
cp target/release/dscc-cli "$stage/"
mkdir -p "$stage/web"
cp -R web/dist "$stage/web/dist"
cp packaging/linux/70-dualsense-command-center.rules "$stage/"
cat > "$stage/README.txt" <<'README'
DualSense Command Center Linux beta archive.
Contents:
- dscc-cli: CLI launcher and diagnostics
- dscc-agent: local agent binary used by dscc-cli serve
- web/dist: bundled production web UI
- 70-dualsense-command-center.rules: optional udev rule for desktop hidraw access
Start DSCC from the extracted folder:
./dscc-cli serve --addr 127.0.0.1:43473
Then open http://127.0.0.1:43473/. Vite is not required for release
archives. If you move the web assets, set DSCC_WEB_DIST to the
absolute path of the dist folder before starting the agent.
If the binary reports a missing udev library, install the runtime
package for your distro, such as libudev1 on Debian/Ubuntu or
systemd-libs on Fedora.
Linux hardware output is still beta. Test HID access as a normal user:
./dscc-cli devices list-hid --experimental --probe-open
./dscc-cli devices diagnose
If the controller is visible but cannot be opened without sudo,
install the included udev rule, reload udev, and reconnect:
sudo install -m 0644 70-dualsense-command-center.rules /etc/udev/rules.d/
sudo udevadm control --reload-rules
sudo udevadm trigger
Do not run DSCC with sudo for normal use; fix hidraw permissions
instead so config files stay owned by your user.
WSL can serve the UI, but native Linux is recommended for hardware
testing because WSL does not expose the same HID/Bluetooth model.
Full setup notes: docs/linux-beta.md in the release tag.
README
tar -C "$stage" -czf "$artifact_root/DualSenseCommandCenter-$tag-linux-x86_64-experimental.tar.gz" .
(
cd "$artifact_root"
sha256sum * > SHA256SUMS-linux.txt
)
- uses: actions/upload-artifact@v6
with:
name: linux-bundled-beta
path: target/release-artifacts/linux/*
if-no-files-found: error
draft-release:
name: Create published release
runs-on: ubuntu-latest
needs:
- windows-artifacts
- linux-artifacts
permissions:
contents: write
steps:
- uses: actions/checkout@v6
- name: Download Windows release artifacts
uses: actions/download-artifact@v6
with:
name: windows-unsigned-beta
path: release-artifacts/windows
- name: Download Linux release artifacts
uses: actions/download-artifact@v6
with:
name: linux-bundled-beta
path: release-artifacts/linux
- name: Create release notes
shell: bash
run: |
awk '
/^# DualSense Command Center / {
if (found) {
exit
}
found = 1
}
found {
print
}
' CHANGELOG.md > release-notes.md
if ! grep -q '^# DualSense Command Center ' release-notes.md; then
cat > release-notes.md <<'EOF'
# DualSense Command Center
DualSense Command Center release artifacts.
EOF
fi
cat >> release-notes.md <<'EOF'
## Release Artifacts
Verify downloaded files against the published SHA256 checksum files.
Windows users should choose one installer:
- `standard`: recommended for most users. Includes controller tuning,
profiles, haptics, diagnostics, telemetry, and Steam Input support.
It does not bundle the optional non-Steam Input Bridge broker.
- `bridge`: includes the self-contained HIDMaestro broker for DSCC
Input Bridge testing with local non-Steam apps. Larger download.
- `bridge-framework-dependent`: advanced bridge build for machines
that already have the matching x64 .NET runtime installed. Smaller
than `bridge`, less plug-and-play than `standard`.
The Linux archive includes the bundled production web UI, so
`./dscc-cli serve --addr 127.0.0.1:43473` can serve the app without
running Vite. Linux HID permissions and hardware validation remain
beta-readiness work. See `docs/linux-beta.md` in this release tag for
udev setup, WSL notes, and artifact validation commands.
EOF
- name: Create published release and upload artifacts
env:
GH_TOKEN: ${{ github.token }}
shell: bash
run: |
set -euo pipefail
mapfile -t files < <(find release-artifacts -type f | sort)
declare -A expected_assets=()
for file in "${files[@]}"; do
expected_assets["$(basename "$file")"]=1
done
if gh release view "${GITHUB_REF_NAME}" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then
gh release edit "${GITHUB_REF_NAME}" \
--repo "${GITHUB_REPOSITORY}" \
--draft=false \
--prerelease=false \
--latest \
--title "DualSense Command Center ${GITHUB_REF_NAME}" \
--notes-file release-notes.md
while IFS=$'\t' read -r asset_id asset_name; do
if [[ -n "${asset_id}" && -z "${expected_assets[$asset_name]:-}" ]]; then
gh api \
--method DELETE \
"repos/${GITHUB_REPOSITORY}/releases/assets/${asset_id}"
fi
done < <(
gh api "repos/${GITHUB_REPOSITORY}/releases/tags/${GITHUB_REF_NAME}" \
--jq '.assets[] | [.id, .name] | @tsv'
)
gh release upload "${GITHUB_REF_NAME}" "${files[@]}" \
--repo "${GITHUB_REPOSITORY}" \
--clobber
else
gh release create "${GITHUB_REF_NAME}" "${files[@]}" \
--repo "${GITHUB_REPOSITORY}" \
--latest \
--title "DualSense Command Center ${GITHUB_REF_NAME}" \
--notes-file release-notes.md
fi