From 942d9578384ff17b490d67f55cf9aaa74eda6ac4 Mon Sep 17 00:00:00 2001 From: snowyukitty <270071858+snowyukitty@users.noreply.github.com> Date: Mon, 31 Aug 2026 23:08:49 +0900 Subject: [PATCH 1/2] build(linux): add AppImage update information Closes #292 --- .github/workflows/publish.yml | 50 +++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 005153c8..ba7fedf1 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -290,6 +290,23 @@ jobs: fi echo "All notarization secrets present" + - name: Configure Linux AppImage update information + if: contains(matrix.platform, 'ubuntu') + shell: bash + run: | + set -euo pipefail + case "${{ matrix.arch }}" in + x86_64) APPIMAGE_RELEASE_ARCH=amd64 ;; + aarch64) APPIMAGE_RELEASE_ARCH=aarch64 ;; + *) + echo "ERROR: unsupported Linux AppImage architecture: ${{ matrix.arch }}" + exit 1 + ;; + esac + REPOSITORY="${GITHUB_REPOSITORY#*/}" + LDAI_UPDATE_INFORMATION="gh-releases-zsync|${GITHUB_REPOSITORY_OWNER}|${REPOSITORY}|latest|DashBeam_${VERSION}_${APPIMAGE_RELEASE_ARCH}.AppImage.zsync" + echo "LDAI_UPDATE_INFORMATION=${LDAI_UPDATE_INFORMATION}" >> "$GITHUB_ENV" + # Retries only help transient latest.json upload races. Keep low — each attempt # re-runs the full tauri build (wasm + frontend + rust + bundles), which turned a # deterministic WiX/MSI failure into ~40+ minutes of useless rebuilds. @@ -317,6 +334,39 @@ jobs: uploadUpdaterJson: true retryAttempts: 2 + - name: Verify and upload Linux AppImage update sidecar + if: contains(matrix.platform, 'ubuntu') + shell: bash + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + RELEASE_TAG="$GITHUB_REF_NAME" + export GH_REPO="$GITHUB_REPOSITORY" + shopt -s nullglob + SIDECARS=(src-tauri/target/release/bundle/appimage/*.AppImage.zsync) + if [ "${#SIDECARS[@]}" -ne 1 ]; then + echo "ERROR: expected exactly one Linux AppImage zsync sidecar, found ${#SIDECARS[@]}" + exit 1 + fi + + SIDECAR="${SIDECARS[0]}" + APPIMAGE="${SIDECAR%.zsync}" + EXPECTED_SIDECAR="${LDAI_UPDATE_INFORMATION##*|}" + if [ ! -f "$APPIMAGE" ] || [ "$(basename "$SIDECAR")" != "$EXPECTED_SIDECAR" ]; then + echo "ERROR: zsync sidecar does not match the expected AppImage update information" + exit 1 + fi + + UPD_INFO_DUMP="$(readelf --string-dump=.upd_info "$APPIMAGE")" + mapfile -t EMBEDDED_UPD_INFO < <(sed -n 's/^[[:space:]]*\[[^]]*\][[:space:]]*//p' <<< "$UPD_INFO_DUMP") + if [ "${#EMBEDDED_UPD_INFO[@]}" -ne 1 ] || [ "${EMBEDDED_UPD_INFO[0]}" != "$LDAI_UPDATE_INFORMATION" ]; then + echo "ERROR: AppImage .upd_info does not exactly match LDAI_UPDATE_INFORMATION" + exit 1 + fi + + gh release upload "$RELEASE_TAG" "$SIDECAR" --clobber + - name: Package Windows portable ZIP if: matrix.platform == 'windows-latest' shell: bash From 6dbc7ec8ffef6c5cec5e81ea79866398efdb5ccc Mon Sep 17 00:00:00 2001 From: snowyukitty <270071858+snowyukitty@users.noreply.github.com> Date: Mon, 7 Sep 2026 18:05:53 +0900 Subject: [PATCH 2/2] build(linux): make the embedded zsync filename release-independent The update information named this build's own version, so an installed AppImage would resolve `latest` and then look for its own sidecar in a release that no longer has one: a 0.7.1 build asking for `DashBeam_0.7.1_amd64.AppImage.zsync` finds only `DashBeam_0.7.2_amd64.AppImage.zsync` there. That is the case the field exists for, so updates would never have been found. The filename field takes `*` as a wildcard, so the embedded pattern is now `DashBeam_*_.AppImage.zsync`. The validation keeps the exact check it had, against a separate `APPIMAGE_EXPECTED_SIDECAR` built from `$VERSION`, so a bundle named for the wrong version is still refused. A second check then asserts the uploaded asset is matched by the pattern the AppImage carries, which is the invariant the first one no longer covers. --- .github/workflows/publish.yml | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 81aef7cc..3f69ae6e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -304,8 +304,17 @@ jobs: ;; esac REPOSITORY="${GITHUB_REPOSITORY#*/}" - LDAI_UPDATE_INFORMATION="gh-releases-zsync|${GITHUB_REPOSITORY_OWNER}|${REPOSITORY}|latest|DashBeam_${VERSION}_${APPIMAGE_RELEASE_ARCH}.AppImage.zsync" - echo "LDAI_UPDATE_INFORMATION=${LDAI_UPDATE_INFORMATION}" >> "$GITHUB_ENV" + # The embedded filename is matched against the assets of whichever + # release is "latest" when an installed AppImage checks for updates, so + # it must not name this build's version: a 0.7.1 AppImage asking for + # DashBeam_0.7.1_amd64.AppImage.zsync would never find the 0.7.2 + # sidecar that replaced it. "*" is a wildcard in this field. + APPIMAGE_SIDECAR_PATTERN="DashBeam_*_${APPIMAGE_RELEASE_ARCH}.AppImage.zsync" + LDAI_UPDATE_INFORMATION="gh-releases-zsync|${GITHUB_REPOSITORY_OWNER}|${REPOSITORY}|latest|${APPIMAGE_SIDECAR_PATTERN}" + { + echo "LDAI_UPDATE_INFORMATION=${LDAI_UPDATE_INFORMATION}" + echo "APPIMAGE_EXPECTED_SIDECAR=DashBeam_${VERSION}_${APPIMAGE_RELEASE_ARCH}.AppImage.zsync" + } >> "$GITHUB_ENV" # Retries only help transient latest.json upload races. Keep low — each attempt # re-runs the full tauri build (wasm + frontend + rust + bundles), which turned a @@ -352,12 +361,23 @@ jobs: SIDECAR="${SIDECARS[0]}" APPIMAGE="${SIDECAR%.zsync}" - EXPECTED_SIDECAR="${LDAI_UPDATE_INFORMATION##*|}" - if [ ! -f "$APPIMAGE" ] || [ "$(basename "$SIDECAR")" != "$EXPECTED_SIDECAR" ]; then - echo "ERROR: zsync sidecar does not match the expected AppImage update information" + SIDECAR_NAME="$(basename "$SIDECAR")" + if [ ! -f "$APPIMAGE" ] || [ "$SIDECAR_NAME" != "$APPIMAGE_EXPECTED_SIDECAR" ]; then + echo "ERROR: expected $APPIMAGE_EXPECTED_SIDECAR beside its AppImage, found $SIDECAR_NAME" exit 1 fi + # The asset has to stay reachable by the pattern the AppImage carries, + # or the update check resolves "latest" and then matches nothing. + SIDECAR_PATTERN="${LDAI_UPDATE_INFORMATION##*|}" + case "$SIDECAR_NAME" in + $SIDECAR_PATTERN) ;; + *) + echo "ERROR: $SIDECAR_NAME is not matched by $SIDECAR_PATTERN" + exit 1 + ;; + esac + UPD_INFO_DUMP="$(readelf --string-dump=.upd_info "$APPIMAGE")" mapfile -t EMBEDDED_UPD_INFO < <(sed -n 's/^[[:space:]]*\[[^]]*\][[:space:]]*//p' <<< "$UPD_INFO_DUMP") if [ "${#EMBEDDED_UPD_INFO[@]}" -ne 1 ] || [ "${EMBEDDED_UPD_INFO[0]}" != "$LDAI_UPDATE_INFORMATION" ]; then