name: Build Fluxer Desktop on: workflow_dispatch: push: branches: - main env: NODE_VERSION: "24" PNPM_VERSION: "10.29.3" BUILD_CHANNEL: "stable" NODE_ENV: "production" FLUXER_DESKTOP_PRODUCTION: "true" VERSION: "1.0.${{ gitea.run_number }}" PUBLIC_BUILD_VERSION: "1.0.${{ gitea.run_number }}" PUBLIC_RELEASE_CHANNEL: "stable" jobs: windows-x64: name: Windows x64 runs-on: rustdesk-windows steps: - name: Clone Fluxer shell: powershell run: | $ErrorActionPreference = "Stop" if (Test-Path "fluxer-src") { Remove-Item "fluxer-src" -Recurse -Force } git clone ` --depth 1 ` --branch main ` https://github.com/fluxerapp/fluxer.git ` fluxer-src if (-not (Test-Path "fluxer-src\fluxer_desktop\src\common\Constants.ts")) { throw "Fluxer source tree is invalid: Constants.ts not found" } Write-Host "Fluxer revision:" git -C fluxer-src rev-parse HEAD Write-Host "Desktop directory:" Get-ChildItem "fluxer-src\fluxer_desktop" - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: "24" - name: Setup pnpm shell: powershell run: | $ErrorActionPreference = "Stop" corepack enable corepack prepare pnpm@10.29.3 --activate node --version pnpm --version - name: Setup Rust shell: powershell run: | $ErrorActionPreference = "Stop" rustup update stable rustup default stable rustup target add x86_64-pc-windows-msvc rustc --version cargo --version - name: Show build tools shell: powershell run: | $ErrorActionPreference = "Stop" Write-Host "Node:" node --version Write-Host "pnpm:" pnpm --version Write-Host "Rust:" rustc --version Write-Host "Cargo:" cargo --version Write-Host "CMake:" cmake --version Write-Host "Ninja:" ninja --version Write-Host "Clang:" clang --version Write-Host "NASM:" nasm --version - name: Configure self-hosted server shell: powershell run: | $ErrorActionPreference = "Stop" $Path = "fluxer-src\fluxer_desktop\src\common\Constants.ts" if (-not (Test-Path $Path)) { throw "Constants.ts not found at $Path" } $Content = Get-Content $Path -Raw $Content = $Content -replace ` "export const STABLE_APP_URL = '[^']+';", ` "export const STABLE_APP_URL = 'https://ds.gandc.ru';" $Content = $Content -replace ` "export const CANARY_APP_URL = '[^']+';", ` "export const CANARY_APP_URL = 'https://ds.gandc.ru';" Set-Content ` -Path $Path ` -Value $Content ` -NoNewline ` -Encoding utf8 Write-Host "Result:" Get-Content $Path $Result = Get-Content $Path -Raw if ($Result -notmatch "STABLE_APP_URL = 'https://ds\.gandc\.ru'") { throw "STABLE_APP_URL was not configured" } if ($Result -notmatch "CANARY_APP_URL = 'https://ds\.gandc\.ru'") { throw "CANARY_APP_URL was not configured" } $PackagePath = "fluxer-src\fluxer_desktop\package.json" if (-not (Test-Path $PackagePath)) { throw "package.json not found at $PackagePath" } $Package = Get-Content $PackagePath -Raw | ConvertFrom-Json $Package.homepage = "https://ds.gandc.ru" $Package | ConvertTo-Json -Depth 100 | Set-Content ` -Path $PackagePath ` -Encoding utf8 Write-Host "Desktop homepage configured:" (Get-Content $PackagePath -Raw | ConvertFrom-Json).homepage $WindowPath = "fluxer-src\fluxer_desktop\src\main\Window.ts" if (-not (Test-Path $WindowPath)) { throw "Window.ts not found at $WindowPath" } $WindowContent = Get-Content $WindowPath -Raw $NavigationPattern = "webContents\.on\('will-navigate', \(event, url\) => \{\r?\n\s*if \(!isTrustedOrigin\(url\)\) \{\r?\n\s*event\.preventDefault\(\);\r?\n\s*\}\r?\n\s*\}\);" $NavigationReplacementLines = @( "const isAllowedSsoOrigin = (url: string): boolean => {" "`ttry {" "`t`treturn new URL(url).origin === 'https://sso.gandc.ru';" "`t} catch {" "`t`treturn false;" "`t}" "};" "" "`tconst allowTrustedOrSsoNavigation = (event: Electron.Event, url: string): void => {" "`t`tif (isTrustedOrigin(url) || isAllowedSsoOrigin(url)) {" "`t`t`treturn;" "`t`t}" "" "`t`tlogger.warn('Blocked navigation outside trusted and SSO origins', {url});" "`t`tevent.preventDefault();" "`t};" "" "`twebContents.on('will-navigate', allowTrustedOrSsoNavigation);" "`twebContents.on('will-redirect', allowTrustedOrSsoNavigation);" ) $NavigationReplacement = $NavigationReplacementLines -join "`n" $PatchedWindowContent = [regex]::Replace( $WindowContent, $NavigationPattern, $NavigationReplacement, 1 ) if ($PatchedWindowContent -eq $WindowContent) { throw "Unable to patch Window.ts SSO navigation handler" } Set-Content ` -Path $WindowPath ` -Value $PatchedWindowContent ` -NoNewline ` -Encoding utf8 Write-Host "SSO navigation allowlist patch applied" Select-String ` -Path $WindowPath ` -Pattern "sso.gandc.ru|will-redirect|allowTrustedOrSsoNavigation" - name: Force WebRTC ICE policy to all shell: powershell run: | $ErrorActionPreference = "Stop" $Path = "fluxer-src\fluxer_desktop\src\preload\index.ts" if (-not (Test-Path $Path)) { throw "Desktop preload not found at $Path" } $Content = Get-Content $Path -Raw $Anchor = "import {contextBridge, ipcRenderer, webFrame} from 'electron';" if ($Content -notmatch [regex]::Escape($Anchor)) { throw "Unable to locate Electron preload import" } if ($Content -match "installForceAllIceTransportPolicy") { throw "WebRTC ICE policy patch is already present" } $Patch = [Text.Encoding]::UTF8.GetString( [Convert]::FromBase64String( "aW1wb3J0IHtjb250ZXh0QnJpZGdlLCBpcGNSZW5kZXJlciwgd2ViRnJhbWV9IGZyb20gJ2VsZWN0cm9uJzsKCmZ1bmN0aW9uIGluc3RhbGxGb3JjZUFsbEljZVRyYW5zcG9ydFBvbGljeSgpOiB2b2lkIHsKCWNvbnRleHRCcmlkZ2UuZXhlY3V0ZUluTWFpbldvcmxkKHsKCQlmdW5jOiAoKSA9PiB7CgkJCWNvbnN0IE5hdGl2ZVJUQ1BlZXJDb25uZWN0aW9uID0gd2luZG93LlJUQ1BlZXJDb25uZWN0aW9uOwoKCQkJaWYgKHR5cGVvZiBOYXRpdmVSVENQZWVyQ29ubmVjdGlvbiAhPT0gJ2Z1bmN0aW9uJykgewoJCQkJcmV0dXJuOwoJCQl9CgoJCQl3aW5kb3cuUlRDUGVlckNvbm5lY3Rpb24gPSBuZXcgUHJveHkoTmF0aXZlUlRDUGVlckNvbm5lY3Rpb24sIHsKCQkJCWNvbnN0cnVjdChUYXJnZXQsIGFyZ3MsIG5ld1RhcmdldCkgewoJCQkJCWNvbnN0IGNvbmZpZyA9IGFyZ3NbMF0gYXMgUlRDQ29uZmlndXJhdGlvbiB8IHVuZGVmaW5lZDsKCgkJCQkJaWYgKGNvbmZpZz8uaWNlVHJhbnNwb3J0UG9saWN5ID09PSAncmVsYXknKSB7CgkJCQkJCWFyZ3NbMF0gPSB7CgkJCQkJCQkuLi5jb25maWcsCgkJCQkJCQlpY2VUcmFuc3BvcnRQb2xpY3k6ICdhbGwnLAoJCQkJCQl9OwoJCQkJCX0KCgkJCQkJcmV0dXJuIFJlZmxlY3QuY29uc3RydWN0KFRhcmdldCwgYXJncywgbmV3VGFyZ2V0KTsKCQkJCX0sCgkJCX0pOwoKCQkJY29uc29sZS5pbmZvKCdbRmx1eGVyIERlc2t0b3BdIFdlYlJUQyBpY2VUcmFuc3BvcnRQb2xpY3kgZm9yY2VkIHRvIGFsbCcpOwoJCX0sCgl9KTsKfQoKaW5zdGFsbEZvcmNlQWxsSWNlVHJhbnNwb3J0UG9saWN5KCk7" ) ) $Content = $Content.Replace( $Anchor, $Patch ) Set-Content ` -Path $Path ` -Value $Content ` -NoNewline ` -Encoding utf8 $Result = Get-Content $Path -Raw if ($Result -notmatch "installForceAllIceTransportPolicy") { throw "WebRTC ICE policy patch verification failed" } Write-Host "Installed preload WebRTC ICE policy override" Select-String ` -Path $Path ` -Pattern "installForceAllIceTransportPolicy|iceTransportPolicy forced to all" - name: Install dependencies shell: powershell run: | $ErrorActionPreference = "Stop" Set-Location fluxer-src pnpm install --frozen-lockfile - name: Build Fluxer Desktop shell: powershell env: ELECTRON_ARCH: x64 run: | $ErrorActionPreference = "Stop" Set-Location fluxer-src pnpm --filter fluxer_desktop build - name: Package Windows portable shell: powershell env: ELECTRON_ARCH: x64 CSC_IDENTITY_AUTO_DISCOVERY: "false" run: | $ErrorActionPreference = "Stop" Set-Location fluxer-src\fluxer_desktop pnpm exec electron-builder ` --config electron-builder.config.cjs ` --win portable ` --x64 ` --publish never - name: Show Windows artifacts shell: powershell run: | $ErrorActionPreference = "Stop" Get-ChildItem ` -Path "fluxer-src\fluxer_desktop\dist-electron" ` -Recurse | Select-Object FullName, Length - name: Upload Windows artifact uses: actions/upload-artifact@v4.6.2 with: name: fluxer-windows-x64 path: | fluxer-src/fluxer_desktop/dist-electron/*.exe if-no-files-found: error linux-x64: name: Linux x64 runs-on: ubuntu-22.04 steps: - name: Clone Fluxer shell: bash run: | set -euxo pipefail rm -rf fluxer-src git clone \ --depth 1 \ --branch main \ https://github.com/fluxerapp/fluxer.git \ fluxer-src test -f \ fluxer-src/fluxer_desktop/src/common/Constants.ts echo "Fluxer revision:" git -C fluxer-src rev-parse HEAD echo "Desktop directory:" ls -la fluxer-src/fluxer_desktop - name: Install Linux build dependencies shell: bash run: | set -euxo pipefail apt-get update DEBIAN_FRONTEND=noninteractive apt-get install -y \ build-essential \ ca-certificates \ clang \ cmake \ curl \ desktop-file-utils \ dpkg-dev \ fakeroot \ ffmpeg \ git \ libasound2 \ libaom-dev \ libavcodec-dev \ libavfilter-dev \ libavformat-dev \ libavutil-dev \ libcbor-dev \ libdbus-1-dev \ libdav1d-dev \ libde265-dev \ libfido2-dev \ libgbm1 \ libgtk-3-0 \ liblcms2-dev \ libnotify4 \ libnss3 \ libpipewire-0.3-dev \ libpulse-dev \ libsecret-1-0 \ libssl-dev \ libswresample-dev \ libswscale-dev \ libudev-dev \ libvips-dev \ libwayland-dev \ libwebp-dev \ libx11-xcb1 \ libxcb-dri3-0 \ libxcomposite1 \ libxdamage1 \ libxkbcommon-dev \ libxkbcommon0 \ libxrandr2 \ libxshmfence1 \ libxss1 \ libxtst6 \ llvm \ meson \ nasm \ ninja-build \ pkg-config \ python3 \ python3-pip \ rpm \ unzip \ xdg-utils \ xz-utils \ yasm \ zlib1g-dev \ zstd - name: Install modern libfido2 shell: bash run: | set -euxo pipefail rm -rf /tmp/libfido2 git clone \ --depth 1 \ --branch 1.17.0 \ https://github.com/Yubico/libfido2.git \ /tmp/libfido2 cmake \ -S /tmp/libfido2 \ -B /tmp/libfido2/build \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=/usr/local \ -DCMAKE_INSTALL_LIBDIR=lib/x86_64-linux-gnu \ -DBUILD_EXAMPLES=OFF \ -DBUILD_MANPAGES=OFF \ -DBUILD_TOOLS=OFF cmake \ --build /tmp/libfido2/build \ --parallel "$(nproc)" cmake \ --install /tmp/libfido2/build ldconfig echo "=== libfido2 ===" PKG_CONFIG_PATH="/usr/local/lib/x86_64-linux-gnu/pkgconfig:/usr/local/lib/pkgconfig:/usr/lib/x86_64-linux-gnu/pkgconfig" \ pkg-config --modversion libfido2 test -f /usr/local/include/fido/es384.h ls -la /usr/local/include/fido/ echo "=== libraries ===" find /usr/local/lib \ -name 'libfido2*' \ -print - name: Install modern PipeWire shell: bash run: | set -euxo pipefail PIPEWIRE_VERSION="1.0.5" rm -rf /tmp/pipewire git clone \ --depth 1 \ --branch "${PIPEWIRE_VERSION}" \ https://github.com/PipeWire/pipewire.git \ /tmp/pipewire meson setup \ /tmp/pipewire/build \ /tmp/pipewire \ --prefix=/usr/local \ --libdir=lib/x86_64-linux-gnu \ --buildtype=release \ -Ddocs=disabled \ -Dman=disabled \ -Dexamples=disabled \ -Dtests=disabled \ -Dinstalled_tests=disabled \ -Dsession-managers=[] \ -Dgstreamer=disabled \ -Dsystemd=disabled \ -Dselinux=disabled \ -Dpipewire-jack=disabled \ -Dpipewire-v4l2=disabled \ -Dbluez5=disabled \ -Djack=disabled \ -Dv4l2=disabled \ -Dlibcamera=disabled \ -Dvulkan=disabled \ -Dsdl2=disabled \ -Dlibmysofa=disabled \ -Droc=disabled \ -Davahi=disabled \ -Decho-cancel-webrtc=disabled \ -Dlibusb=disabled \ -Draop=disabled \ -Dlv2=disabled \ -Dlibcanberra=disabled \ -Dlibffado=disabled meson compile \ -C /tmp/pipewire/build \ -j "$(nproc)" meson install \ -C /tmp/pipewire/build ldconfig export PKG_CONFIG_PATH="/usr/local/lib/x86_64-linux-gnu/pkgconfig:/usr/local/lib/pkgconfig:/usr/lib/x86_64-linux-gnu/pkgconfig" echo "=== PipeWire ===" pkg-config \ --modversion \ libpipewire-0.3 pkg-config \ --modversion \ libspa-0.2 echo "=== PipeWire CFLAGS ===" pkg-config \ --cflags \ libpipewire-0.3 pkg-config \ --cflags \ libspa-0.2 echo "=== Checking headers ===" grep -R \ "spa_meta_region_is_valid" \ /usr/local/include/spa-0.2/spa \ | head echo "=== PipeWire libraries ===" find /usr/local/lib \ \( -name 'libpipewire-0.3.so*' -o -name 'libspa*.so*' \) \ -print - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: "24" - name: Setup pnpm shell: bash run: | set -euxo pipefail corepack enable corepack prepare pnpm@10.29.3 --activate node --version pnpm --version - name: Setup Rust shell: bash run: | set -euxo pipefail curl \ --proto '=https' \ --tlsv1.2 \ -sSf \ https://sh.rustup.rs | sh -s -- -y --profile minimal echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" "$HOME/.cargo/bin/rustup" update stable "$HOME/.cargo/bin/rustup" default stable "$HOME/.cargo/bin/rustc" --version "$HOME/.cargo/bin/cargo" --version - name: Configure self-hosted server shell: bash run: | set -euxo pipefail python3 <<'PY' from pathlib import Path import re path = Path( "fluxer-src/fluxer_desktop/src/common/Constants.ts" ) if not path.is_file(): raise SystemExit( f"Constants.ts not found: {path}" ) data = path.read_text( encoding="utf-8" ) data, stable_count = re.subn( r"export const STABLE_APP_URL = '[^']+';", "export const STABLE_APP_URL = 'https://ds.gandc.ru';", data, count=1, ) data, canary_count = re.subn( r"export const CANARY_APP_URL = '[^']+';", "export const CANARY_APP_URL = 'https://ds.gandc.ru';", data, count=1, ) if stable_count != 1: raise SystemExit( "Unable to patch STABLE_APP_URL" ) if canary_count != 1: raise SystemExit( "Unable to patch CANARY_APP_URL" ) path.write_text( data, encoding="utf-8" ) result = path.read_text( encoding="utf-8" ) if ( "export const STABLE_APP_URL = " "'https://ds.gandc.ru';" not in result ): raise SystemExit( "STABLE_APP_URL verification failed" ) if ( "export const CANARY_APP_URL = " "'https://ds.gandc.ru';" not in result ): raise SystemExit( "CANARY_APP_URL verification failed" ) print(result) PY python3 <<'PY' from pathlib import Path import json path = Path( "fluxer-src/fluxer_desktop/package.json" ) if not path.is_file(): raise SystemExit( f"package.json not found: {path}" ) package = json.loads( path.read_text( encoding="utf-8" ) ) package["homepage"] = "https://ds.gandc.ru" path.write_text( json.dumps( package, ensure_ascii=False, indent=2, ) + "\n", encoding="utf-8", ) print( "Desktop homepage configured:", package["homepage"], ) PY python3 <<'PY' from pathlib import Path import re path = Path( "fluxer-src/fluxer_desktop/src/main/Window.ts" ) if not path.is_file(): raise SystemExit( f"Window.ts not found: {path}" ) data = path.read_text( encoding="utf-8" ) pattern = re.compile( r"webContents\.on\('will-navigate', \(event, url\) => \{\s*" r"if \(!isTrustedOrigin\(url\)\) \{\s*" r"event\.preventDefault\(\);\s*" r"\}\s*" r"\}\);" ) replacement = "\n".join([ "const isAllowedSsoOrigin = (url: string): boolean => {", "\ttry {", "\t\treturn new URL(url).origin === 'https://sso.gandc.ru';", "\t} catch {", "\t\treturn false;", "\t}", "};", "", "\tconst allowTrustedOrSsoNavigation = (event: Electron.Event, url: string): void => {", "\t\tif (isTrustedOrigin(url) || isAllowedSsoOrigin(url)) {", "\t\t\treturn;", "\t\t}", "", "\t\tlogger.warn('Blocked navigation outside trusted and SSO origins', {url});", "\t\tevent.preventDefault();", "\t};", "", "\twebContents.on('will-navigate', allowTrustedOrSsoNavigation);", "\twebContents.on('will-redirect', allowTrustedOrSsoNavigation);", ]) data, count = pattern.subn( replacement, data, count=1, ) if count != 1: raise SystemExit( "Unable to patch Window.ts SSO navigation handler" ) path.write_text( data, encoding="utf-8", ) print("SSO navigation allowlist patch applied") PY grep -n \ -E 'sso\.gandc\.ru|will-redirect|allowTrustedOrSsoNavigation' \ fluxer-src/fluxer_desktop/src/main/Window.ts - name: Force WebRTC ICE policy to all shell: bash run: | set -euxo pipefail python3 - <<'PY' from pathlib import Path import base64 path = Path( "fluxer-src/fluxer_desktop/" "src/preload/index.ts" ) if not path.is_file(): raise SystemExit( f"Desktop preload not found: {path}" ) data = path.read_text( encoding="utf-8" ) anchor = "import {contextBridge, ipcRenderer, webFrame} from 'electron';" if anchor not in data: raise SystemExit( "Unable to locate Electron preload import" ) if "installForceAllIceTransportPolicy" in data: raise SystemExit( "WebRTC ICE policy patch is already present" ) patch = base64.b64decode( "aW1wb3J0IHtjb250ZXh0QnJpZGdlLCBpcGNSZW5kZXJlciwgd2ViRnJhbWV9IGZyb20gJ2VsZWN0cm9uJzsKCmZ1bmN0aW9uIGluc3RhbGxGb3JjZUFsbEljZVRyYW5zcG9ydFBvbGljeSgpOiB2b2lkIHsKCWNvbnRleHRCcmlkZ2UuZXhlY3V0ZUluTWFpbldvcmxkKHsKCQlmdW5jOiAoKSA9PiB7CgkJCWNvbnN0IE5hdGl2ZVJUQ1BlZXJDb25uZWN0aW9uID0gd2luZG93LlJUQ1BlZXJDb25uZWN0aW9uOwoKCQkJaWYgKHR5cGVvZiBOYXRpdmVSVENQZWVyQ29ubmVjdGlvbiAhPT0gJ2Z1bmN0aW9uJykgewoJCQkJcmV0dXJuOwoJCQl9CgoJCQl3aW5kb3cuUlRDUGVlckNvbm5lY3Rpb24gPSBuZXcgUHJveHkoTmF0aXZlUlRDUGVlckNvbm5lY3Rpb24sIHsKCQkJCWNvbnN0cnVjdChUYXJnZXQsIGFyZ3MsIG5ld1RhcmdldCkgewoJCQkJCWNvbnN0IGNvbmZpZyA9IGFyZ3NbMF0gYXMgUlRDQ29uZmlndXJhdGlvbiB8IHVuZGVmaW5lZDsKCgkJCQkJaWYgKGNvbmZpZz8uaWNlVHJhbnNwb3J0UG9saWN5ID09PSAncmVsYXknKSB7CgkJCQkJCWFyZ3NbMF0gPSB7CgkJCQkJCQkuLi5jb25maWcsCgkJCQkJCQlpY2VUcmFuc3BvcnRQb2xpY3k6ICdhbGwnLAoJCQkJCQl9OwoJCQkJCX0KCgkJCQkJcmV0dXJuIFJlZmxlY3QuY29uc3RydWN0KFRhcmdldCwgYXJncywgbmV3VGFyZ2V0KTsKCQkJCX0sCgkJCX0pOwoKCQkJY29uc29sZS5pbmZvKCdbRmx1eGVyIERlc2t0b3BdIFdlYlJUQyBpY2VUcmFuc3BvcnRQb2xpY3kgZm9yY2VkIHRvIGFsbCcpOwoJCX0sCgl9KTsKfQoKaW5zdGFsbEZvcmNlQWxsSWNlVHJhbnNwb3J0UG9saWN5KCk7" ).decode("utf-8") data = data.replace( anchor, patch, 1, ) path.write_text( data, encoding="utf-8", ) result = path.read_text( encoding="utf-8" ) if "installForceAllIceTransportPolicy" not in result: raise SystemExit( "WebRTC ICE policy patch verification failed" ) print( "Installed preload WebRTC ICE policy override" ) PY grep -n \ -E 'installForceAllIceTransportPolicy|iceTransportPolicy forced to all' \ fluxer-src/fluxer_desktop/src/preload/index.ts - name: Install dependencies shell: bash run: | set -euxo pipefail cd fluxer-src pnpm install --frozen-lockfile - name: Build Fluxer Desktop shell: bash env: ELECTRON_ARCH: x64 PKG_CONFIG_PATH: /usr/local/lib/x86_64-linux-gnu/pkgconfig:/usr/local/lib/pkgconfig:/usr/lib/x86_64-linux-gnu/pkgconfig LD_LIBRARY_PATH: /usr/local/lib/x86_64-linux-gnu:/usr/local/lib run: | set -euxo pipefail echo "libfido2 version:" pkg-config --modversion libfido2 echo "libfido2 include flags:" pkg-config --cflags libfido2 echo "Checking ES384 header:" test -f /usr/local/include/fido/es384.h cd fluxer-src pnpm --filter fluxer_desktop build - name: Inspect Linux native libraries shell: bash run: | set -euxo pipefail cd fluxer-src echo "Searching for WebAuthn native module..." find fluxer_desktop \ -type f \ \( -name '*.node' -o -name 'libfido2.so*' \) \ -print echo "Dynamic dependencies:" while IFS= read -r file; do echo "===== $file =====" ldd "$file" || true done < <( find fluxer_desktop \ -type f \ -name '*.node' ) - name: Package Linux shell: bash env: ELECTRON_ARCH: x64 CSC_IDENTITY_AUTO_DISCOVERY: "false" run: | set -euxo pipefail cd fluxer-src/fluxer_desktop pnpm exec electron-builder \ --config electron-builder.config.cjs \ --linux AppImage deb \ --x64 \ --publish never - name: Show Linux artifacts shell: bash run: | set -euxo pipefail find \ fluxer-src/fluxer_desktop/dist-electron \ -maxdepth 2 \ -type f \ -printf '%p %s bytes\n' | sort - name: Upload Linux artifacts uses: actions/upload-artifact@v4.6.2 with: name: fluxer-linux-x64 path: | fluxer-src/fluxer_desktop/dist-electron/*.AppImage fluxer-src/fluxer_desktop/dist-electron/*.deb if-no-files-found: error