Skip to content

release: prepare v1.2.1-ell.7 rollback beta #7

release: prepare v1.2.1-ell.7 rollback beta

release: prepare v1.2.1-ell.7 rollback beta #7

Workflow file for this run

name: Linux x86-64 AppImage
on:
workflow_dispatch:
inputs:
version:
description: Release version used for the validation package
required: true
type: string
default: v1.2.1-ell.7
push:
tags:
- 'v*-ell.*'
# A tag build adds an AppImage, checksum, and checksummed runtime source/relink
# companion to the same DRAFT prerelease used by the Windows workflow. Manual
# dispatch builds and validates without publishing. A human publishes only
# after both platform assets and their matching source records are inspected.
permissions:
contents: read
concurrency:
group: linux-appimage-${{ github.ref }}
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-24.04
timeout-minutes: 180
permissions:
contents: write
env:
BUILD_DIR: build-appimage
DOLRECOMP_BUILD_DIR: build-dolrecomp-appimage
CCACHE_DIR: ${{ github.workspace }}/.cache/ccache-appimage
TOOL_DIR: ${{ github.workspace }}/.cache/appimage-tools
BUILD_IMAGE: ringout-appimage-build:debian12
CONTAINER_CCACHE_DIR: /src/.cache/ccache-appimage
CONTAINER_TOOL_DIR: /src/.cache/appimage-tools
APPIMAGETOOL_FILE: appimagetool-1.9.1-x86_64.AppImage
APPIMAGE_RUNTIME_FILE: runtime-x86_64-asset-456065460
TYPE2_SOURCE_FILE: type2-runtime-75849dce7cc37e4319b633df1f116ca895c71a12.tar.gz
LIBFUSE_SOURCE_FILE: fuse-3.15.0.tar.xz
SQUASHFUSE_SOURCE_FILE: squashfuse-0.5.2.tar.gz
steps:
- name: Check out exact revision
uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
with:
fetch-depth: 0
persist-credentials: false
- name: Restore canonical tag ref
run: .github/scripts/restore-canonical-tag-ref.sh
- name: Test release tag provenance helper
run: .github/scripts/tests/test-restore-canonical-tag-ref.sh
- name: Test source-bound draft publisher
run: .github/scripts/tests/test-publish-tag-draft-assets.sh
- name: Resolve and validate version
id: version
env:
DISPATCH_VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
if [[ "$GITHUB_EVENT_NAME" == push && "$GITHUB_REF_TYPE" == tag ]]; then
version="$GITHUB_REF_NAME"
else
version="$DISPATCH_VERSION"
fi
[[ "$version" =~ ^v[0-9][0-9A-Za-z._+-]*$ ]] || {
echo "invalid release version: $version" >&2
exit 1
}
echo "version=$version" >>"$GITHUB_OUTPUT"
- name: Set up cached Docker builder
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
- name: Build recorded Debian 12 build environment
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: .
file: .github/containers/appimage-debian12.Containerfile
load: true
tags: ${{ env.BUILD_IMAGE }}
cache-from: type=gha,scope=ringout-appimage-debian12-v1
cache-to: type=gha,mode=max,scope=ringout-appimage-debian12-v1
- name: Restore compiler cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ${{ env.CCACHE_DIR }}
key: ringout-${{ runner.os }}-debian12-appimage-release-ccache-v1-${{ github.sha }}
restore-keys: |
ringout-${{ runner.os }}-debian12-appimage-release-ccache-v1-
- name: Configure compiler cache
run: |
set -euo pipefail
mkdir -p "$CCACHE_DIR"
container=(docker run --rm
--user "$(id -u):$(id -g)"
--volume "$GITHUB_WORKSPACE:/src"
--workdir /src
--env "CCACHE_DIR=$CONTAINER_CCACHE_DIR"
--env CCACHE_BASEDIR=/src
--env CCACHE_COMPILERCHECK=content)
"${container[@]}" "$BUILD_IMAGE" ccache --set-config=max_size=1.5G
"${container[@]}" "$BUILD_IMAGE" ccache --set-config=compression=true
"${container[@]}" "$BUILD_IMAGE" ccache --set-config=compression_level=5
"${container[@]}" "$BUILD_IMAGE" ccache --zero-stats
- name: Configure Linux runtime
run: |
set -euo pipefail
docker run --rm \
--user "$(id -u):$(id -g)" \
--volume "$GITHUB_WORKSPACE:/src" \
--workdir /src \
--env "CCACHE_DIR=$CONTAINER_CCACHE_DIR" \
--env CCACHE_BASEDIR=/src \
--env CCACHE_COMPILERCHECK=content \
--env CFLAGS=-ffile-prefix-map=/src=. \
--env CXXFLAGS=-ffile-prefix-map=/src=. \
"$BUILD_IMAGE" \
cmake -S ModernGekko -B "$BUILD_DIR" -GNinja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DLINUX_LOCAL_DEV=ON \
-DENABLE_QT=OFF \
-DENABLE_TESTS=OFF \
-DENABLE_ANALYTICS=OFF \
-DENABLE_AUTOUPDATE=OFF \
-DBUILD_TESTING=ON \
-DMODERNGEKKO_ENABLE_DOLPHIN_TESTS=OFF
- name: Build Linux runtime and tests
run: |
set -euo pipefail
docker run --rm \
--user "$(id -u):$(id -g)" \
--volume "$GITHUB_WORKSPACE:/src" \
--workdir /src \
--env "CCACHE_DIR=$CONTAINER_CCACHE_DIR" \
--env CCACHE_BASEDIR=/src \
--env CCACHE_COMPILERCHECK=content \
"$BUILD_IMAGE" \
cmake --build "$BUILD_DIR" \
--target moderngekko-launcher moderngekko-module-info moderngekko-tests \
--parallel "$(nproc)"
- name: Test Linux runtime
run: >-
docker run --rm
--user "$(id -u):$(id -g)"
--volume "$GITHUB_WORKSPACE:/src"
--workdir /src
"$BUILD_IMAGE"
ctest --test-dir "$BUILD_DIR" --output-on-failure -j"$(nproc)"
- name: Configure static DolRecomp
run: |
set -euo pipefail
docker run --rm \
--user "$(id -u):$(id -g)" \
--volume "$GITHUB_WORKSPACE:/src" \
--workdir /src \
--env "CCACHE_DIR=$CONTAINER_CCACHE_DIR" \
--env CCACHE_BASEDIR=/src \
--env CCACHE_COMPILERCHECK=content \
--env CFLAGS=-ffile-prefix-map=/src=. \
"$BUILD_IMAGE" \
cmake -S DolRecomp -B "$DOLRECOMP_BUILD_DIR" -GNinja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_EXE_LINKER_FLAGS=-static \
-DZLIB_USE_STATIC_LIBS=ON
- name: Build and test static DolRecomp
run: |
set -euo pipefail
container=(docker run --rm
--user "$(id -u):$(id -g)"
--volume "$GITHUB_WORKSPACE:/src"
--workdir /src
--env "CCACHE_DIR=$CONTAINER_CCACHE_DIR"
--env CCACHE_BASEDIR=/src
--env CCACHE_COMPILERCHECK=content)
"${container[@]}" "$BUILD_IMAGE" \
cmake --build "$DOLRECOMP_BUILD_DIR" --parallel "$(nproc)"
"${container[@]}" "$BUILD_IMAGE" \
ctest --test-dir "$DOLRECOMP_BUILD_DIR" --output-on-failure -j"$(nproc)"
"${container[@]}" "$BUILD_IMAGE" \
readelf -l "$DOLRECOMP_BUILD_DIR/dolrecomp" >dolrecomp-program-headers.txt
if grep -q INTERP dolrecomp-program-headers.txt; then
echo "dolrecomp is dynamically linked; AppImage first-run setup would not be portable" >&2
exit 1
fi
rm dolrecomp-program-headers.txt
- name: Show compiler cache statistics
if: always()
run: >-
docker run --rm
--user "$(id -u):$(id -g)"
--volume "$GITHUB_WORKSPACE:/src"
--workdir /src
--env "CCACHE_DIR=$CONTAINER_CCACHE_DIR"
"$BUILD_IMAGE"
ccache --show-stats
- name: Cache pinned AppImage construction tools
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ${{ env.TOOL_DIR }}
key: ringout-appimage-tools-appimagetool-1.9.1-runtime-asset-456065460-sources-v2
- name: Download and verify pinned AppImage construction tools
run: |
set -euo pipefail
mkdir -p "$TOOL_DIR"
if [[ ! -f "$TOOL_DIR/$APPIMAGETOOL_FILE" ]]; then
curl --fail --location --retry 5 --retry-all-errors \
--output "$TOOL_DIR/$APPIMAGETOOL_FILE" \
https://github.com/AppImage/appimagetool/releases/download/1.9.1/appimagetool-x86_64.AppImage
fi
if [[ ! -f "$TOOL_DIR/$APPIMAGE_RUNTIME_FILE" ]]; then
curl --fail --location --retry 5 --retry-all-errors \
-H 'Accept: application/octet-stream' \
-H 'X-GitHub-Api-Version: 2022-11-28' \
--output "$TOOL_DIR/$APPIMAGE_RUNTIME_FILE" \
https://api.github.com/repos/AppImage/type2-runtime/releases/assets/456065460
fi
if [[ ! -f "$TOOL_DIR/$TYPE2_SOURCE_FILE" ]]; then
curl --fail --location --retry 5 --retry-all-errors \
--output "$TOOL_DIR/$TYPE2_SOURCE_FILE" \
https://codeload.github.com/AppImage/type2-runtime/tar.gz/75849dce7cc37e4319b633df1f116ca895c71a12
fi
if [[ ! -f "$TOOL_DIR/$LIBFUSE_SOURCE_FILE" ]]; then
curl --fail --location --retry 5 --retry-all-errors \
--output "$TOOL_DIR/$LIBFUSE_SOURCE_FILE" \
https://github.com/libfuse/libfuse/releases/download/fuse-3.15.0/fuse-3.15.0.tar.xz
fi
if [[ ! -f "$TOOL_DIR/$SQUASHFUSE_SOURCE_FILE" ]]; then
curl --fail --location --retry 5 --retry-all-errors \
--output "$TOOL_DIR/$SQUASHFUSE_SOURCE_FILE" \
https://github.com/vasi/squashfuse/archive/0.5.2.tar.gz
fi
cd "$TOOL_DIR"
sha256sum -c <<EOF
ed4ce84f0d9caff66f50bcca6ff6f35aae54ce8135408b3fa33abfc3cb384eb0 $APPIMAGETOOL_FILE
1cc49bcf1e2ccd593c379adb17c9f85a36d619088296504de95b1d06215aebbf $APPIMAGE_RUNTIME_FILE
b7af4960da4b90364e935a3281d04fad6560da4813c012414fa2f738291ad443 $TYPE2_SOURCE_FILE
70589cfd5e1cff7ccd6ac91c86c01be340b227285c5e200baa284e401eea2ca0 $LIBFUSE_SOURCE_FILE
db0238c5981dabbd80ee09ae15387f390091668ca060a7bc38047912491443d3 $SQUASHFUSE_SOURCE_FILE
EOF
chmod 755 "$APPIMAGETOOL_FILE" "$APPIMAGE_RUNTIME_FILE"
- name: Assemble and validate AppImage
env:
SOURCE_REPOSITORY: https://github.com/${{ github.repository }}
SOURCE_COMMIT: ${{ github.sha }}
PACKAGE_VALIDATION_ONLY: ${{ github.event_name == 'workflow_dispatch' && '1' || '0' }}
run: |
set -euo pipefail
docker run --rm \
--user "$(id -u):$(id -g)" \
--volume "$GITHUB_WORKSPACE:/src" \
--workdir /src \
--env "SOURCE_REPOSITORY=$SOURCE_REPOSITORY" \
--env "SOURCE_COMMIT=$SOURCE_COMMIT" \
--env "PACKAGE_VALIDATION_ONLY=$PACKAGE_VALIDATION_ONLY" \
"$BUILD_IMAGE" \
.github/scripts/package-appimage.sh \
--version '${{ steps.version.outputs.version }}' \
--build-dir "$BUILD_DIR" \
--dolrecomp-build-dir "$DOLRECOMP_BUILD_DIR" \
--out-dir dist/out \
--appimagetool "$CONTAINER_TOOL_DIR/$APPIMAGETOOL_FILE" \
--appimage-runtime "$CONTAINER_TOOL_DIR/$APPIMAGE_RUNTIME_FILE" \
--type2-source "$CONTAINER_TOOL_DIR/$TYPE2_SOURCE_FILE" \
--libfuse-source "$CONTAINER_TOOL_DIR/$LIBFUSE_SOURCE_FILE" \
--squashfuse-source "$CONTAINER_TOOL_DIR/$SQUASHFUSE_SOURCE_FILE"
- name: Test exact AppImage without FUSE on clean Ubuntu 24.04
run: |
set -euo pipefail
shopt -s nullglob
images=(dist/out/RingOut-*-linux-x86_64.AppImage)
[[ ${#images[@]} -eq 1 ]] || {
echo "expected exactly one AppImage, found ${#images[@]}" >&2
exit 1
}
artifact="$(realpath "${images[0]}")"
docker run --rm \
--network none \
--cap-drop ALL \
--security-opt no-new-privileges \
--user 65534:65534 \
--env APPIMAGE_EXTRACT_AND_RUN=1 \
--env RINGOUT_DATA_DIR=/tmp/ringout-selftest \
--volume "$artifact:/RingOut.AppImage:ro" \
ubuntu@sha256:33ceb71981b602c1a7443a53469e4dba065f7503eab3078a2d7a57a2ab987517 \
/RingOut.AppImage --ringout-self-test
- name: Summarize validated package
run: |
set -euo pipefail
shopt -s nullglob
images=(dist/out/RingOut-*-linux-x86_64.AppImage)
[[ ${#images[@]} -eq 1 ]] || {
echo "expected exactly one AppImage, found ${#images[@]}" >&2
exit 1
}
file_version='${{ steps.version.outputs.version }}'
file_version=${file_version#v}
source_bundle="dist/out/RingOut-$file_version-appimage-runtime-sources.tar.zst"
[[ -s "${images[0]}.sha256" ]] || {
echo "missing AppImage checksum: ${images[0]}.sha256" >&2
exit 1
}
[[ -s "$source_bundle" && -s "$source_bundle.sha256" ]] || {
echo "missing AppImage runtime corresponding-source bundle or checksum" >&2
exit 1
}
validate_sidecar() {
local payload=$1 sidecar="$1.sha256" expected actual
expected="$(sha256sum "$payload" | awk '{print $1}') $(basename "$payload")"
actual=$(<"$sidecar")
[[ $(wc -l <"$sidecar") -eq 1 && "$actual" == "$expected" ]] || {
echo "malformed or incorrect checksum sidecar: $sidecar" >&2
exit 1
}
(
cd dist/out
sha256sum -c "$(basename "$sidecar")"
)
}
validate_sidecar "${images[0]}"
validate_sidecar "$source_bundle"
{
echo '### Linux AppImage'
echo
echo "Version: \`${{ steps.version.outputs.version }}\`"
echo "Package: \`$(basename "${images[0]}")\`"
echo "Package size: \`$(stat -c '%s' "${images[0]}") bytes\`"
echo "SHA-256: \`$(sha256sum "${images[0]}" | awk '{print $1}')\`"
echo "Runtime source/relink bundle: \`$(basename "$source_bundle")\`"
echo "Source bundle size: \`$(stat -c '%s' "$source_bundle") bytes\`"
echo "Source bundle SHA-256: \`$(sha256sum "$source_bundle" | awk '{print $1}')\`"
echo
echo 'Validated: AppDir manifest, exact Debian DSO/source closure, runtime source/relink bundle, source provenance, static recompiler, Sys resources, glibc floor, privacy/no-game-data policy, synthetic DOL module build/load, extraction, and clean-Ubuntu no-FUSE self-test.'
if [[ "$GITHUB_EVENT_NAME" == workflow_dispatch ]]; then
echo
echo '> Manual runs are validation-only and have no retained download. Push a `v*-ell.*` tag to create draft-release assets.'
fi
} >>"$GITHUB_STEP_SUMMARY"
- name: Publish immutable assets to source-bound draft prerelease
if: github.event_name == 'push' && github.ref_type == 'tag'
env:
GITHUB_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ steps.version.outputs.version }}
run: |
set -euo pipefail
shopt -s nullglob
assets=(dist/out/RingOut-*-linux-x86_64.AppImage)
[[ ${#assets[@]} -eq 1 ]] || exit 1
assets+=("${assets[0]}.sha256")
file_version=${RELEASE_TAG#v}
source_bundles=(dist/out/RingOut-*-appimage-runtime-sources.tar.zst)
source_checksums=(dist/out/RingOut-*-appimage-runtime-sources.tar.zst.sha256)
[[ ${#source_bundles[@]} -eq 1 && ${#source_checksums[@]} -eq 1 ]] || {
echo "expected exactly one AppImage runtime source bundle/checksum pair" >&2
exit 1
}
expected_source="dist/out/RingOut-$file_version-appimage-runtime-sources.tar.zst"
[[ "${source_bundles[0]}" == "$expected_source" &&
"${source_checksums[0]}" == "$expected_source.sha256" ]] || {
echo "AppImage runtime source bundle name does not match release $RELEASE_TAG" >&2
exit 1
}
assets+=("${source_bundles[0]}" "${source_checksums[0]}")
.github/scripts/publish-tag-draft-assets.sh \
--join-release \
--tag "$RELEASE_TAG" \
"${assets[@]}"
- name: Upload CMake diagnostics on failure
if: failure()
continue-on-error: true
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: linux-appimage-cmake-logs
retention-days: 7
if-no-files-found: ignore
path: |
build-appimage/CMakeCache.txt
build-appimage/CMakeFiles/CMakeConfigureLog.yaml
build-dolrecomp-appimage/CMakeCache.txt
build-dolrecomp-appimage/CMakeFiles/CMakeConfigureLog.yaml