Build & Release #103
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build & Release | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| make_release: | |
| description: 'Create Github Release?' | |
| type: boolean | |
| required: true | |
| default: false | |
| version: | |
| description: "Release version tag (e.g. v1.0.0)" | |
| required: false | |
| default: "v1.0.0" | |
| build_windows_x64: | |
| description: 'Build Windows x64?' | |
| type: boolean | |
| required: false | |
| default: true | |
| build_windows_arm64: | |
| description: 'Build Windows ARM64?' | |
| type: boolean | |
| required: false | |
| default: true | |
| build_win7: | |
| description: 'Build Windows 7 (x64 + x86)?' | |
| type: boolean | |
| required: false | |
| default: true | |
| build_macos: | |
| description: 'Build macOS universal?' | |
| type: boolean | |
| required: false | |
| default: true | |
| build_linux: | |
| description: 'Build Linux (amd64/.deb/.rpm)?' | |
| type: boolean | |
| required: false | |
| default: true | |
| permissions: | |
| contents: write | |
| jobs: | |
| build-windows-x64: | |
| runs-on: windows-latest | |
| if: ${{ github.event.inputs.build_windows_x64 == 'true' }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Setup Python | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.11" | |
| cache: "pip" | |
| - name: Setup MSVC 14.40 toolset | |
| uses: ilammy/msvc-dev-cmd@v1 | |
| with: | |
| toolset: 14.40 | |
| - name: Install dependencies | |
| run: pip install . | |
| - name: Build PyInstaller bootloader from source | |
| env: | |
| PYINSTALLER_COMPILE_BOOTLOADER: "1" | |
| run: | | |
| pip download --no-binary pyinstaller --no-deps --no-cache-dir -d pyinstaller_src "pyinstaller==6.10.0" | |
| pip install (Get-ChildItem pyinstaller_src\*.tar.gz).FullName | |
| - name: Build EXE with PyInstaller | |
| run: pyinstaller packaging/windows.spec --noconfirm | |
| - name: Strip Rich PE header | |
| shell: bash | |
| run: | | |
| python -c " | |
| import struct, pathlib | |
| exe = pathlib.Path('dist/TgWsProxy.exe') | |
| data = bytearray(exe.read_bytes()) | |
| rich = data.find(b'Rich') | |
| if rich == -1: | |
| raise SystemExit('Rich header not found') | |
| ck = struct.unpack_from('<I', data, rich + 4)[0] | |
| dans = struct.pack('<I', 0x536E6144 ^ ck) | |
| ds = data.find(dans) | |
| if ds == -1: | |
| raise SystemExit('DanS marker not found') | |
| data[ds:rich + 8] = b'\x00' * (rich + 8 - ds) | |
| exe.write_bytes(data) | |
| print(f'Stripped Rich header: offset {ds}..{rich+8}') | |
| " | |
| - name: Rename artifact | |
| run: mv dist/TgWsProxy.exe dist/TgWsProxy_windows.exe | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: TgWsProxy-windows-x64 | |
| path: dist/TgWsProxy_windows.exe | |
| build-windows-arm64: | |
| runs-on: windows-11-arm | |
| if: ${{ github.event.inputs.build_windows_arm64 == 'true' }} | |
| env: | |
| CRYPTOGRAPHY_VERSION: "46.0.5" | |
| ARM64_WHEELHOUSE: wheelhouse-arm64 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Setup Python | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.11" | |
| architecture: arm64 | |
| cache: "pip" | |
| - name: Restore ARM64 cryptography wheel | |
| id: cryptography-wheel-cache | |
| uses: actions/cache@v4 | |
| with: | |
| path: ${{ env.ARM64_WHEELHOUSE }} | |
| key: windows-arm64-py311-cryptography-${{ env.CRYPTOGRAPHY_VERSION }}-${{ hashFiles('pyproject.toml', '.github/workflows/build.yml') }} | |
| - name: Install ARM64 OpenSSL | |
| if: steps.cryptography-wheel-cache.outputs.cache-hit != 'true' | |
| shell: pwsh | |
| run: | | |
| vcpkg install openssl:arm64-windows-static | |
| $opensslDir = "$env:VCPKG_INSTALLATION_ROOT\installed\arm64-windows-static" | |
| "OPENSSL_DIR=$opensslDir" >> $env:GITHUB_ENV | |
| "OPENSSL_STATIC=1" >> $env:GITHUB_ENV | |
| "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" >> $env:GITHUB_ENV | |
| - name: Build ARM64 cryptography wheel | |
| if: steps.cryptography-wheel-cache.outputs.cache-hit != 'true' | |
| run: | | |
| mkdir $env:ARM64_WHEELHOUSE | |
| pip wheel --no-deps --wheel-dir $env:ARM64_WHEELHOUSE "cryptography==$env:CRYPTOGRAPHY_VERSION" | |
| - name: Install dependencies & pyinstaller | |
| run: pip install --find-links $env:ARM64_WHEELHOUSE . "pyinstaller==6.13.0" | |
| - name: Build EXE with PyInstaller | |
| run: pyinstaller packaging/windows.spec --noconfirm | |
| - name: Strip Rich PE header | |
| shell: bash | |
| run: | | |
| python -c " | |
| import struct, pathlib | |
| exe = pathlib.Path('dist/TgWsProxy.exe') | |
| data = bytearray(exe.read_bytes()) | |
| rich = data.find(b'Rich') | |
| if rich == -1: | |
| print('Rich header not found, skipping') | |
| raise SystemExit(0) | |
| ck = struct.unpack_from('<I', data, rich + 4)[0] | |
| dans = struct.pack('<I', 0x536E6144 ^ ck) | |
| ds = data.find(dans) | |
| if ds == -1: | |
| print('DanS marker not found, skipping') | |
| raise SystemExit(0) | |
| data[ds:rich + 8] = b'\x00' * (rich + 8 - ds) | |
| exe.write_bytes(data) | |
| print(f'Stripped Rich header: offset {ds}..{rich+8}') | |
| " | |
| - name: Rename artifact | |
| run: mv dist/TgWsProxy.exe dist/TgWsProxy_windows_arm64.exe | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: TgWsProxy-windows-arm64 | |
| path: dist/TgWsProxy_windows_arm64.exe | |
| build-win7: | |
| runs-on: windows-latest | |
| if: ${{ github.event.inputs.build_win7 == 'true' }} | |
| strategy: | |
| matrix: | |
| include: | |
| - arch: x64 | |
| suffix: 64bit | |
| - arch: x86 | |
| suffix: 32bit | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.8" | |
| architecture: ${{ matrix.arch }} | |
| cache: "pip" | |
| - name: Install dependencies & pyinstaller | |
| run: pip install . "pyinstaller==5.13.2" | |
| - name: Build EXE with PyInstaller | |
| run: pyinstaller packaging/windows.spec --noconfirm | |
| - name: Strip Rich PE header | |
| shell: bash | |
| run: | | |
| python -c " | |
| import struct, pathlib | |
| exe = pathlib.Path('dist/TgWsProxy.exe') | |
| data = bytearray(exe.read_bytes()) | |
| rich = data.find(b'Rich') | |
| if rich == -1: | |
| raise SystemExit('Rich header not found') | |
| ck = struct.unpack_from('<I', data, rich + 4)[0] | |
| dans = struct.pack('<I', 0x536E6144 ^ ck) | |
| ds = data.find(dans) | |
| if ds == -1: | |
| raise SystemExit('DanS marker not found') | |
| data[ds:rich + 8] = b'\x00' * (rich + 8 - ds) | |
| exe.write_bytes(data) | |
| print(f'Stripped Rich header: offset {ds}..{rich+8}') | |
| " | |
| - name: Rename artifact | |
| run: mv dist/TgWsProxy.exe dist/TgWsProxy_windows_7_${{ matrix.suffix }}.exe | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: TgWsProxy-win7-${{ matrix.suffix }} | |
| path: dist/TgWsProxy_windows_7_${{ matrix.suffix }}.exe | |
| build-macos: | |
| runs-on: macos-latest | |
| if: ${{ github.event.inputs.build_macos == 'true' }} | |
| env: | |
| CFFI_VERSION: "2.0.0" | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Install universal2 Python | |
| run: | | |
| set -euo pipefail | |
| curl -LO https://www.python.org/ftp/python/3.12.10/python-3.12.10-macos11.pkg | |
| sudo installer -pkg python-3.12.10-macos11.pkg -target / | |
| echo "/Library/Frameworks/Python.framework/Versions/3.12/bin" >> "$GITHUB_PATH" | |
| - name: Install dependencies | |
| run: | | |
| set -euo pipefail | |
| python3.12 -m pip install --upgrade pip setuptools wheel | |
| python3.12 -m pip install delocate==0.13.0 | |
| mkdir -p wheelhouse/arm64 wheelhouse/x86_64 wheelhouse/universal2 | |
| python3.12 -m pip download \ | |
| --only-binary=:all: \ | |
| --platform macosx_11_0_arm64 \ | |
| --python-version 3.12 \ | |
| --implementation cp \ | |
| -d wheelhouse/arm64 \ | |
| "cffi==$CFFI_VERSION" \ | |
| Pillow==12.1.0 \ | |
| psutil==7.0.0 | |
| python3.12 -m pip download \ | |
| --only-binary=:all: \ | |
| --platform macosx_10_13_x86_64 \ | |
| --python-version 3.12 \ | |
| --implementation cp \ | |
| -d wheelhouse/x86_64 \ | |
| "cffi==$CFFI_VERSION" \ | |
| Pillow==12.1.0 | |
| python3.12 -m pip download \ | |
| --only-binary=:all: \ | |
| --platform macosx_10_9_x86_64 \ | |
| --python-version 3.12 \ | |
| --implementation cp \ | |
| -d wheelhouse/x86_64 \ | |
| psutil==7.0.0 | |
| delocate-merge \ | |
| wheelhouse/arm64/cffi-*.whl \ | |
| wheelhouse/x86_64/cffi-*.whl \ | |
| -w wheelhouse/universal2 | |
| delocate-merge \ | |
| wheelhouse/arm64/pillow-12.1.0-*.whl \ | |
| wheelhouse/x86_64/pillow-12.1.0-*.whl \ | |
| -w wheelhouse/universal2 | |
| delocate-merge \ | |
| wheelhouse/arm64/psutil-7.0.0-*.whl \ | |
| wheelhouse/x86_64/psutil-7.0.0-*.whl \ | |
| -w wheelhouse/universal2 | |
| python3.12 -m pip install --no-deps wheelhouse/universal2/*.whl | |
| python3.12 -m pip install . | |
| python3.12 -m pip install pyinstaller==6.13.0 | |
| - name: Validate macOS GUI dependencies | |
| run: | | |
| python3.12 -m pip check | |
| python3.12 -m py_compile macos.py | |
| python3.12 -c "import AppKit, customtkinter, macos, pystray, tkinter" | |
| - name: Create macOS icon | |
| run: | | |
| set -euo pipefail | |
| python3.12 macos.py --render-app-icon icon.icns | |
| - name: Build app with PyInstaller | |
| run: python3.12 -m PyInstaller packaging/macos.spec --noconfirm | |
| - name: Validate universal2 app bundle | |
| run: | | |
| set -euo pipefail | |
| ICON_FILE="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIconFile' \ | |
| 'dist/TG WS Proxy.app/Contents/Info.plist')" | |
| test -n "$ICON_FILE" | |
| test -f "dist/TG WS Proxy.app/Contents/Resources/$ICON_FILE" | |
| test -d "dist/TG WS Proxy.app/Contents/Resources/customtkinter" | |
| found=0 | |
| while IFS= read -r -d '' file; do | |
| if file "$file" | grep -q "Mach-O"; then | |
| found=1 | |
| archs="$(lipo -archs "$file" 2>/dev/null || true)" | |
| case "$archs" in | |
| *arm64*x86_64*|*x86_64*arm64*) ;; | |
| *) | |
| echo "Missing universal2 slices in $file: ${archs:-unknown}" >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| fi | |
| done < <(find "dist/TG WS Proxy.app" -type f -print0) | |
| if [ "$found" -eq 0 ]; then | |
| echo "No Mach-O files found in app bundle" >&2 | |
| exit 1 | |
| fi | |
| - name: Create DMG | |
| run: | | |
| set -euo pipefail | |
| packaging/dmg/build_dmg.sh \ | |
| "dist/TG WS Proxy.app" \ | |
| "TG WS Proxy" \ | |
| "dist/TgWsProxy_macos_universal.dmg" | |
| - name: Validate DMG | |
| run: | | |
| set -euo pipefail | |
| for DMG in "dist/TgWsProxy_macos_universal.dmg"; do | |
| MOUNT_DIR="$(mktemp -d)" | |
| DEVICE="$(hdiutil attach \ | |
| -readonly \ | |
| -nobrowse \ | |
| -mountpoint "$MOUNT_DIR" \ | |
| "$DMG" \ | |
| | awk '/^\/dev\// { print $1; exit }')" | |
| test -d "$MOUNT_DIR/TG WS Proxy.app" | |
| test -L "$MOUNT_DIR/Applications" | |
| test "$(readlink "$MOUNT_DIR/Applications")" = "/Applications" | |
| test -f "$MOUNT_DIR/.background/background.tiff" | |
| test -f "$MOUNT_DIR/.DS_Store" | |
| hdiutil detach "$DEVICE" | |
| rmdir "$MOUNT_DIR" | |
| done | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: TgWsProxy-macOS | |
| path: dist/TgWsProxy_macos_universal.dmg | |
| build-linux: | |
| runs-on: ubuntu-22.04 | |
| if: ${{ github.event.inputs.build_linux == 'true' }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Install system dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y \ | |
| python3-venv \ | |
| python3-dev \ | |
| python3-gi \ | |
| python3-gi-cairo \ | |
| gir1.2-appindicator3-0.1 \ | |
| gir1.2-ayatanaappindicator3-0.1 \ | |
| python3-tk | |
| - name: Create venv with system site-packages | |
| run: python3 -m venv --system-site-packages .venv | |
| - name: Install dependencies | |
| run: | | |
| .venv/bin/pip install --upgrade pip | |
| .venv/bin/pip install . | |
| .venv/bin/pip install "pyinstaller==6.13.0" | |
| - name: Build binary with PyInstaller | |
| run: .venv/bin/pyinstaller packaging/linux.spec --noconfirm | |
| - name: Rename binary artifact | |
| run: mv dist/TgWsProxy dist/TgWsProxy_linux_amd64 | |
| - name: Create .deb package | |
| run: | | |
| set -euo pipefail | |
| VERSION="${{ github.event.inputs.version }}" | |
| VERSION="${VERSION#v}" | |
| PKG_ROOT="pkg" | |
| rm -rf "$PKG_ROOT" | |
| mkdir -p \ | |
| "$PKG_ROOT/DEBIAN" \ | |
| "$PKG_ROOT/usr/bin" \ | |
| "$PKG_ROOT/usr/share/applications" \ | |
| "$PKG_ROOT/usr/share/icons/hicolor/256x256/apps" | |
| install -m 755 dist/TgWsProxy_linux_amd64 "$PKG_ROOT/usr/bin/tg-ws-proxy" | |
| .venv/bin/python - <<PY | |
| from PIL import Image | |
| Image.open("icon.ico").save( | |
| "${PKG_ROOT}/usr/share/icons/hicolor/256x256/apps/tg-ws-proxy.png", | |
| "PNG", | |
| ) | |
| PY | |
| cat > "$PKG_ROOT/usr/share/applications/tg-ws-proxy.desktop" <<EOF | |
| [Desktop Entry] | |
| Type=Application | |
| Name=TG WS Proxy | |
| GenericName=Telegram Proxy | |
| Comment=Telegram Desktop WebSocket Bridge Proxy | |
| Exec=tg-ws-proxy | |
| Icon=tg-ws-proxy | |
| Terminal=false | |
| Categories=Network; | |
| StartupNotify=true | |
| Keywords=telegram;proxy;websocket; | |
| EOF | |
| cat > "$PKG_ROOT/DEBIAN/control" <<EOF | |
| Package: tg-ws-proxy | |
| Version: ${VERSION} | |
| Section: net | |
| Priority: optional | |
| Architecture: amd64 | |
| Maintainer: Flowseal | |
| Depends: libgtk-3-0, libayatana-appindicator3-1, python3-tk | |
| Description: Telegram Desktop WebSocket Bridge Proxy | |
| MTProto/WebSocket bridge proxy for Telegram Desktop with tray UI. | |
| EOF | |
| dpkg-deb --build --root-owner-group \ | |
| "$PKG_ROOT" \ | |
| "dist/TgWsProxy_linux_amd64.deb" | |
| - name: Create .rpm package with fpm | |
| run: | | |
| set -euo pipefail | |
| VERSION="${{ github.event.inputs.version }}" | |
| VERSION="${VERSION#v}" | |
| sudo gem install fpm -v 1.17.0 | |
| mkdir -p rpm_package/usr/bin | |
| mkdir -p rpm_package/usr/share/applications | |
| mkdir -p rpm_package/usr/share/icons/hicolor/256x256/apps | |
| cp dist/TgWsProxy_linux_amd64 rpm_package/usr/bin/tg-ws-proxy | |
| chmod 755 rpm_package/usr/bin/tg-ws-proxy | |
| .venv/bin/python - <<PY | |
| from PIL import Image | |
| Image.open("icon.ico").save( | |
| "rpm_package/usr/share/icons/hicolor/256x256/apps/tg-ws-proxy.png", | |
| "PNG", | |
| ) | |
| PY | |
| cat > rpm_package/usr/share/applications/tg-ws-proxy.desktop <<EOF | |
| [Desktop Entry] | |
| Type=Application | |
| Name=TG WS Proxy | |
| GenericName=Telegram Proxy | |
| Comment=Telegram Desktop WebSocket Bridge Proxy | |
| Exec=tg-ws-proxy | |
| Icon=tg-ws-proxy | |
| Terminal=false | |
| Categories=Network; | |
| StartupNotify=true | |
| Keywords=telegram;proxy;websocket; | |
| EOF | |
| cat > post_install.sh <<EOF | |
| #!/bin/bash | |
| if [ -x /usr/bin/update-desktop-database ]; then | |
| /usr/bin/update-desktop-database &> /dev/null || : | |
| fi | |
| if [ -x /usr/bin/gtk-update-icon-cache ]; then | |
| /usr/bin/gtk-update-icon-cache -q /usr/share/icons/hicolor &> /dev/null || : | |
| fi | |
| EOF | |
| chmod +x post_install.sh | |
| fpm -s dir \ | |
| -t rpm \ | |
| -n tg-ws-proxy \ | |
| -v ${VERSION} \ | |
| --iteration 1 \ | |
| --architecture x86_64 \ | |
| --license "MIT" \ | |
| --vendor "Flowseal" \ | |
| --maintainer "Flowseal" \ | |
| --url "https://github.com/Flowseal/tg-ws-proxy" \ | |
| --description "MTProto/WebSocket bridge proxy for Telegram Desktop with tray UI." \ | |
| --depends "libgtk-3.so.0()(64bit)" \ | |
| --depends "libayatana-appindicator3.so.1()(64bit)" \ | |
| --depends "python3-tkinter" \ | |
| --after-install post_install.sh \ | |
| --after-remove post_install.sh \ | |
| -C rpm_package \ | |
| . | |
| mv tg-ws-proxy-${VERSION}-1.x86_64.rpm dist/TgWsProxy_linux_amd64.rpm | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: TgWsProxy-linux | |
| path: | | |
| dist/TgWsProxy_linux_amd64 | |
| dist/TgWsProxy_linux_amd64.deb | |
| dist/TgWsProxy_linux_amd64.rpm | |
| release: | |
| needs: [build-windows-x64, build-windows-arm64, build-win7, build-macos, build-linux] | |
| runs-on: ubuntu-latest | |
| if: >- | |
| ${{ github.event.inputs.make_release == 'true' | |
| && github.event.inputs.build_windows_x64 == 'true' | |
| && github.event.inputs.build_windows_arm64 == 'true' | |
| && github.event.inputs.build_win7 == 'true' | |
| && github.event.inputs.build_macos == 'true' | |
| && github.event.inputs.build_linux == 'true' }} | |
| steps: | |
| - uses: actions/download-artifact@v8 | |
| with: | |
| pattern: TgWsProxy* | |
| path: dist | |
| merge-multiple: true | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ github.event.inputs.version }} | |
| name: "TG WS Proxy ${{ github.event.inputs.version }}" | |
| body: | | |
| --- | |
| ### [❤️ Поддержать развитие проекта](https://github.com/Flowseal/tg-ws-proxy/blob/main/docs/Funding.md) | |
| > [!TIP] | |
| > Не можете скачать? | |
| > Добавьте `185.199.109.133 release-assets.githubusercontent.com` в hosts или воспользуйтесь зеркалом: https://sourceforge.net/projects/tg-ws-proxy.mirror/files/ | |
| files: | | |
| dist/TgWsProxy_windows.exe | |
| dist/TgWsProxy_windows_arm64.exe | |
| dist/TgWsProxy_windows_7_64bit.exe | |
| dist/TgWsProxy_windows_7_32bit.exe | |
| dist/TgWsProxy_macos_universal.dmg | |
| dist/TgWsProxy_linux_amd64 | |
| dist/TgWsProxy_linux_amd64.deb | |
| dist/TgWsProxy_linux_amd64.rpm | |
| draft: false | |
| prerelease: false | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |