diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 862ab8d..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,479 +0,0 @@ -# ───────────────────────────────────────────────────────────────────────────── -# release.yml — build all 5 patched-Firefox targets at $0 and publish them as -# DRAFT GitHub Release assets, named per the wrapper contract (constants.ARCHIVE_NAME). -# DRAFT on purpose: a human runs the realness gate and only THEN un-drafts + bumps -# BINARY_VERSION. Nothing auto-ships (issue #14 lesson). -# -# PACKAGING (issue #14: dangling symlinks broke 265 downloads — never again): -# Linux → cp -aL (dereference ALL symlinks into real files) + rm dev tools + -# strip + sanitize + tar at ROOT, then validate_release.py as a HARD -# in-pipeline gate (the exact battle-tested script from the source repo). -# Win → mach package; zip the CONTENTS of dist/firefox (clean tree, NOT -# dist/bin) so firefox.exe sits at the zip ROOT. -# macOS → mach package; ad-hoc codesign the .app; PRESERVE its internal relative -# symlinks (a .app legitimately has them — cp -aL would break it); verify -# every symlink is relative+internal; tar the bundle. --version self-gate. -# -# DRIVE GATE (the firefox-8 catcher): after build, every binary is DRIVEN by -# Playwright on its native runner (launch via juggler + real page + JS roundtrip, -# headless, no screenshot → GPU-free, zero proxy). A juggler-less binary renders -# a screenshot fine but is undrivable — only an actual drive catches that. The -# proxy realness gate (fppro/webrtc) stays LOCAL — it needs secrets. -# -# Trigger: push a tag `firefox-N`, or run manually. Hybrid runners, all free. -# ───────────────────────────────────────────────────────────────────────────── -name: release - -on: - push: - tags: ['firefox-*'] - workflow_dispatch: - inputs: - source_ref: - description: 'firefox_antidetect_patch ref to build' - default: 'stealth/150' - release_tag: - description: 'release tag to publish the draft under (e.g. firefox-9)' - required: true - -env: - SOURCE_REPO: feder-cr/firefox_antidetect_patch - SOURCE_REF: ${{ github.event.inputs.source_ref || 'stealth/150' }} - -jobs: - build: - name: build-${{ matrix.leg }} - runs-on: ${{ matrix.runner }} - timeout-minutes: 350 - strategy: - fail-fast: false - matrix: - include: - - leg: linux-x86_64 - runner: ubuntu-24.04 - family: linux - target: '' - rust_target: x86_64-unknown-linux-gnu - win_disables: 'no' - extra_pkgs: '' - asset: firefox-150.0.1-stealth-linux-x86_64.tar.gz - - leg: linux-arm64 - runner: ubuntu-24.04-arm - family: linux - target: '' - rust_target: aarch64-unknown-linux-gnu - win_disables: 'no' - extra_pkgs: '' - asset: firefox-150.0.1-stealth-linux-arm64.tar.gz - - leg: win-x86_64 - runner: ubuntu-24.04 - family: win - target: x86_64-pc-windows-msvc - rust_target: x86_64-pc-windows-msvc - win_disables: 'yes' - extra_pkgs: 'msitools p7zip-full zip' - asset: firefox-150.0.1-stealth-win-x86_64.zip - - leg: macos-arm64 - runner: macos-15 - family: mac - target: aarch64-apple-darwin - rust_target: aarch64-apple-darwin - win_disables: 'no' - extra_pkgs: '' - asset: firefox-150.0.1-stealth-macos-arm64.tar.gz - - leg: macos-x86_64 - runner: macos-15-intel - family: mac - target: x86_64-apple-darwin - rust_target: x86_64-apple-darwin - win_disables: 'no' - extra_pkgs: '' - asset: firefox-150.0.1-stealth-macos-x86_64.tar.gz - steps: - - name: Free disk + 16G swap (Linux runners) - if: matrix.family != 'mac' - run: | - sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android \ - /usr/local/share/boost "${AGENT_TOOLSDIRECTORY:-/opt/hostedtoolcache}" 2>/dev/null || true - sudo fallocate -l 16G /swapfile && sudo chmod 600 /swapfile && sudo mkswap /swapfile && sudo swapon /swapfile || true - - - name: Checkout patched Firefox source - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - repository: ${{ env.SOURCE_REPO }} - ref: ${{ env.SOURCE_REF }} - fetch-depth: 1 - - # Record which firefox_antidetect_patch commit this build came from. The publish - # job turns the range previous-release..this commit into the release notes - # (scripts/gen_release_notes.py), and re-publishes it as a source-commit.txt - # asset so the NEXT release knows where to start the changelog. One leg is - # enough — all legs check out the same SOURCE_REF. - - name: Record source commit (for auto release notes) - if: matrix.leg == 'linux-x86_64' - shell: bash - run: git rev-parse HEAD > source-commit.txt && cat source-commit.txt - - name: Upload source-commit artifact - if: matrix.leg == 'linux-x86_64' - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - with: - name: source-commit - path: source-commit.txt - if-no-files-found: error - retention-days: 7 - - - name: Set up Python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 - with: { python-version: '3.11' } - - - name: Install Linux build tools - if: matrix.family != 'mac' - run: | - sudo apt-get update - sudo apt-get install -y util-linux binutils ${{ matrix.extra_pkgs }} - - - name: Select Xcode 26.2 + export SDK path (macOS) - if: matrix.family == 'mac' - run: | - sudo xcode-select -s /Applications/Xcode_26.2.app - SDKP="$(xcrun --show-sdk-path)" - echo "SDK_PATH=$SDKP" >> "$GITHUB_ENV" - echo "macOS SDK $(xcrun --sdk macosx --show-sdk-version) at $SDKP" - - - name: Add Rust target - run: rustup target add ${{ matrix.rust_target }} || true - - - name: Extend the repo .mozconfig (NO mold; +target/SDK as needed) - run: | - test -f .mozconfig || { echo "ERROR: no .mozconfig in source"; exit 1; } - rm -f mozconfig - { - echo "" - echo "# --- release CI levers for ${{ matrix.leg }} (mold intentionally OFF — it segfaults libxul) ---" - echo "ac_add_options --disable-debug-symbols" - } >> .mozconfig - if [ -n "${{ matrix.target }}" ]; then echo "ac_add_options --target=${{ matrix.target }}" >> .mozconfig; fi - if [ "${{ matrix.family }}" = "mac" ]; then echo "ac_add_options --with-macos-sdk=$SDK_PATH" >> .mozconfig; fi - if [ "${{ matrix.win_disables }}" = "yes" ]; then - { echo "ac_add_options --disable-default-browser-agent"; - echo "ac_add_options --disable-maintenance-service"; - echo "ac_add_options --disable-update-agent"; } >> .mozconfig - fi - if [ "${{ matrix.family }}" = "mac" ]; then NCPU=$(sysctl -n hw.ncpu); else NCPU=4; fi - { echo "mk_add_options MOZ_PARALLEL_BUILD=$NCPU"; - echo "mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/obj-rel"; } >> .mozconfig - echo "----- final .mozconfig -----"; cat .mozconfig - - - name: Build - run: ./mach build - - # ── LINUX: dereference symlinks (issue #14) + strip + sanitize + tar@root + GATE - - name: Package + validate (Linux) - if: matrix.family == 'linux' - run: | - set -e - DIST=obj-rel/dist/bin - STAGING=staging - rm -rf "$STAGING"; mkdir -p "$STAGING" out - cp -aL "$DIST/." "$STAGING/" # -L: dereference ALL symlinks into real files - N=$(find "$STAGING" -type l | wc -l) - [ "$N" -eq 0 ] || { echo "ERROR: $N symlinks remain after cp -aL"; exit 1; } - for t in xpcshell certutil pk12util rapl; do rm -f "$STAGING/$t"; done - # JUGGLER GATE: the binary is undrivable by Playwright without it (see 70-known-bugs) - { [ -e "$STAGING/chrome/juggler.manifest" ] && [ -d "$STAGING/chrome/juggler" ]; } \ - || { echo "ERROR: juggler missing from package (chrome/juggler) — Playwright can't drive it"; exit 1; } - echo "juggler GATE OK (loose chrome/juggler present)" - find "$STAGING" -type f \ - \( -name '*.so' -o -name firefox -o -name firefox-bin -o -name plugin-container \ - -o -name pingsender -o -name glxtest -o -name vaapitest -o -name updater \) \ - -exec strip --strip-debug {} + 2>/dev/null || true - STAGING="$STAGING" python3 scripts/linux_sanitize.py || true # no-op in CI (no /home/feder), defensive - tar --owner=0 --group=0 --numeric-owner --mtime="2026-01-01 00:00:00 UTC" \ - -czf "out/${{ matrix.asset }}" -C "$STAGING" . # firefox at ROOT - echo "=== HARD GATE: scripts/validate_release.py (the issue-#14 protector) ===" - python3 scripts/validate_release.py --linux "out/${{ matrix.asset }}" --linux-only - ls -la out/ - - # ── WINDOWS (cross): zip the CLEAN dist/firefox tree, firefox.exe at root - - name: Package (Windows cross) - if: matrix.family == 'win' - run: | - set -e - # Do NOT swallow a mach failure: `./mach package || echo` lets set -e pass - # and would fall through to a stale tree. A release MUST come from the clean - # dist/firefox; dist/bin is the dev tree (cruft + loose juggler that masked - # the firefox-7/8 packaging bugs), never acceptable for a release. - ./mach package - [ -f obj-rel/dist/firefox/firefox.exe ] \ - || { echo "ERROR: mach package did not produce a clean dist/firefox tree"; exit 1; } - WIN_APP=obj-rel/dist/firefox - echo "packaging from: $WIN_APP" - # JUGGLER GATE: omni.ja must carry juggler (else Playwright can't drive it) - [ -f "$WIN_APP/omni.ja" ] || { echo "ERROR: no omni.ja in $WIN_APP"; exit 1; } - python3 -c "import zipfile,sys; sys.exit(0 if any('juggler' in n.lower() for n in zipfile.ZipFile('$WIN_APP/omni.ja').namelist()) else 1)" \ - || { echo "ERROR: juggler missing from $WIN_APP/omni.ja — Playwright can't drive it"; exit 1; } - echo "juggler GATE OK (win)" - mkdir -p out - ( cd "$WIN_APP" && zip -qr "$GITHUB_WORKSPACE/out/${{ matrix.asset }}" . ) # firefox.exe at zip ROOT - ls -la out/ - - # ── macOS: package .app, ad-hoc sign, verify relative-internal symlinks, --version gate, tar - - name: Package + validate (macOS) - if: matrix.family == 'mac' - run: | - set -e - ./mach package - APP="$(find obj-rel/dist -maxdepth 2 -name '*.app' -type d | head -1)" - [ -n "$APP" ] || { echo "ERROR: no .app produced"; exit 1; } - echo "built app: $APP" - # JUGGLER GATE: the .app's omni.ja must carry juggler (else Playwright can't drive it) - python3 -c "import zipfile,sys,glob; jas=glob.glob('$APP/Contents/Resources/omni.ja')+glob.glob('$APP/Contents/Resources/browser/omni.ja'); sys.exit(0 if jas and any(any('juggler' in n.lower() for n in zipfile.ZipFile(j).namelist()) for j in jas) else 1)" \ - || { echo "ERROR: juggler missing from .app omni.ja — Playwright can't drive it"; exit 1; } - echo "juggler GATE OK (mac)" - codesign --force --deep --sign - --timestamp=none "$APP" - codesign --verify --deep --strict --verbose=2 "$APP" - echo "=== --version GATE ===" - "$APP/Contents/MacOS/firefox" --version - echo "=== critical files present ===" - for need in "Contents/MacOS/firefox" "Contents/Info.plist"; do - [ -e "$APP/$need" ] || { echo "ERROR: missing $need"; exit 1; } - done - echo "=== Info.plist well-formed + required keys (a malformed plist → Finder 'damaged') ===" - plutil -lint "$APP/Contents/Info.plist" - for key in CFBundleExecutable CFBundleIdentifier CFBundleShortVersionString; do - plutil -extract "$key" raw -o - "$APP/Contents/Info.plist" >/dev/null \ - || { echo "ERROR: Info.plist missing $key"; exit 1; } - done - EXEC="$(plutil -extract CFBundleExecutable raw -o - "$APP/Contents/Info.plist")" - [ -e "$APP/Contents/MacOS/$EXEC" ] \ - || { echo "ERROR: CFBundleExecutable '$EXEC' has no matching binary in Contents/MacOS"; exit 1; } - echo "=== verify NO absolute symlinks in the .app (relative-internal ones are fine) ===" - BAD="$(find "$APP" -type l -print0 | xargs -0 -I{} sh -c 't=$(readlink "{}"); case "$t" in /*) echo "{} -> $t";; esac')" - [ -z "$BAD" ] || { echo "ERROR: absolute symlinks in .app (break on user machines):"; echo "$BAD" | head -5; exit 1; } - echo "mac .app OK: critical files present, no absolute symlinks" - STABLE="$(dirname "$APP")/Firefox.app" - [ "$APP" = "$STABLE" ] || mv "$APP" "$STABLE" - mkdir -p out - tar -czf "out/${{ matrix.asset }}" -C "$(dirname "$STABLE")" Firefox.app # preserves internal symlinks - ls -la out/ - - - name: Upload build artifact - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - with: - name: asset-${{ matrix.leg }} - path: out/${{ matrix.asset }} - if-no-files-found: error - retention-days: 7 - - # DRIVE GATE — the firefox-8 catcher. A raw `firefox --screenshot` proves - # nothing about automation: a juggler-less binary renders fine and ships - # broken (firefox-8 did exactly that). So we DRIVE every binary the way users - # will: Playwright launches it over the juggler pipe, loads a real page, and - # round-trips JS. A binary missing/broken juggler throws TargetClosedError - # here and the release never publishes. Headless, NO screenshot → GPU-free, - # so it can't false-fail on the GPU-less hosted runners. Zero proxy / zero - # secrets → safe in public CI (the proxy realness gate stays local, by design). - # Each leg runs on its NATIVE runner so we test the real artifact, not a cross - # surrogate. Playwright is pinned to a version validated against this build's - # juggler; bump it in lockstep when the juggler is re-synced from upstream. - gate: - name: gate-${{ matrix.leg }} - needs: build - runs-on: ${{ matrix.runner }} - timeout-minutes: 25 - strategy: - fail-fast: false - matrix: - include: - # `extra: --full` adds the mouse/keyboard/canvas/navsurface interaction - # checks. Only on linux-x86_64 (historically the most reliable hosted - # runner): the interaction code is platform-identical JS (omni.ja), so - # one reliable full run catches a firefox-2-class regression for all - # platforms. The other legs run SMOKE (launch+http+UA+webdriver) — the - # firefox-8/juggler catcher — which is robust even on the flaky - # windows-latest runner. See scripts/ci_drive_gate.py. - - leg: linux-x86_64 - runner: ubuntu-24.04 - kind: linux - asset: firefox-150.0.1-stealth-linux-x86_64.tar.gz - extra: '--full' - - leg: linux-arm64 - runner: ubuntu-24.04-arm - kind: linux - asset: firefox-150.0.1-stealth-linux-arm64.tar.gz - extra: '' - - leg: win-x86_64 - runner: windows-latest - kind: win - asset: firefox-150.0.1-stealth-win-x86_64.zip - extra: '' - - leg: macos-arm64 - runner: macos-15 - kind: mac - asset: firefox-150.0.1-stealth-macos-arm64.tar.gz - extra: '' - - leg: macos-x86_64 - runner: macos-15-intel - kind: mac - asset: firefox-150.0.1-stealth-macos-x86_64.tar.gz - extra: '' - steps: - - name: Checkout wrapper (for scripts/ci_drive_gate.py) - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: { fetch-depth: 1 } - - name: Download asset - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 - with: - name: asset-${{ matrix.leg }} - path: art - - name: Set up Python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 - with: { python-version: '3.11' } - - name: Install Playwright driver (no bundled browser — we override executable_path) - # Pin from a SINGLE source (scripts/playwright_pin.txt) so release.yml and - # verify-assets.yml can't drift to different versions. The drive gate then - # ENFORCES playwright↔juggler compatibility: an incompatible pin fails the - # launch/drive (TargetClosedError / protocol error) and nothing publishes. - # Bump the pin file in lockstep when the juggler is re-synced from upstream. - shell: bash - run: python -m pip install --quiet "playwright==$(cat scripts/playwright_pin.txt)" - - name: Linux system deps for headless firefox - if: matrix.kind == 'linux' - run: sudo "$(which python)" -m playwright install-deps firefox - - name: Extract + locate firefox binary - shell: bash - run: | - set -e - mkdir -p ff - A="art/${{ matrix.asset }}" - case "${{ matrix.kind }}" in - win) python -c "import zipfile; zipfile.ZipFile('$A').extractall('ff')"; EXE="ff/firefox.exe";; - linux) tar xzf "$A" -C ff; EXE="ff/firefox";; - mac) tar xzf "$A" -C ff; EXE="ff/Firefox.app/Contents/MacOS/firefox";; - esac - [ -e "$EXE" ] || { echo "ERROR: firefox binary not found at $EXE"; exit 1; } - chmod +x "$EXE" 2>/dev/null || true - echo "FF_EXE=$EXE" >> "$GITHUB_ENV" - echo "located: $EXE" - - name: DRIVE GATE — Playwright launch via juggler + real page (+ interaction on --full) - shell: bash - run: python scripts/ci_drive_gate.py "$FF_EXE" ${{ matrix.extra }} - - # CLOAK + WEBGL-MASKING GUARDS — run the wrapper's e2e cloak/gamma checks - # against THIS leg's freshly-built artifact, on its native runner. The - # wrapper's headless=True is headed+hidden (cloak on Win/macOS, its own - # Xvfb on Linux). Linux (Xvfb + llvmpipe) and Windows (WARP) give a - # software WebGL context on the GPU-less hosts, so the WebGL-dependent - # assertions run there. macOS GitHub runners expose NO WebGL in the CI - # session at all (even vanilla Firefox; macOS has no software-GL fallback), - # so on the mac legs the WebGL checks self-skip and the cloak is validated - # via its non-blank screenshot + CGWindowAlpha == 0. test_cloak asserts the - # window is hidden (Windows DWMWA_CLOAKED / macOS CGWindowAlpha) AND still - # renders — the macOS leg is the only place the cocoa cloak patch gets RUN. - # The webgl guard catches a regression of the gamma readPixels noise back to - # the pixelscan-maskable ±1 spike form (covered on Linux + Windows). - - name: Install pyobjc Quartz (macOS — to read the cloak window alpha) - if: matrix.kind == 'mac' - run: python -m pip install --quiet pyobjc-framework-Quartz - - name: Cloak + WebGL-masking guards (headed) - shell: bash - run: | - python -m pip install --quiet ".[dev]" - INVPW_BINARY_PATH="$FF_EXE" python -m pytest \ - tests/test_cloak.py \ - "tests/test_fingerprint_surface.py::test_webgl_readpixels_no_masking_signature" \ - -m e2e -o addopts='' -q - - publish: - name: publish-draft-release - needs: [build, gate] - runs-on: ubuntu-24.04 - permissions: - contents: write - steps: - - name: Checkout wrapper (for scripts/gen_release_notes.py) - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: { fetch-depth: 1 } - - name: Set up Python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 - with: { python-version: '3.11' } - - name: Download all build assets - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 - with: { pattern: asset-*, path: dl, merge-multiple: true } - - name: Download source-commit metadata - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 - with: { name: source-commit, path: src-meta } - - name: Assert all 5 target archives present (no silent partial release) - run: | - cd dl - EXPECTED=" - firefox-150.0.1-stealth-linux-x86_64.tar.gz - firefox-150.0.1-stealth-linux-arm64.tar.gz - firefox-150.0.1-stealth-win-x86_64.zip - firefox-150.0.1-stealth-macos-arm64.tar.gz - firefox-150.0.1-stealth-macos-x86_64.tar.gz - " - for a in $EXPECTED; do - [ -s "$a" ] || { echo "ERROR: missing/empty release asset: $a (a build leg silently dropped out?)"; exit 1; } - done - echo "all 5 target archives present" - - name: Generate checksums.txt - run: | - cd dl; ls -la - # explicit glob — never include checksums.txt itself (the `*`-includes-itself trap) - sha256sum firefox-150.0.1-stealth-* > checksums.txt - echo "----- checksums.txt -----"; cat checksums.txt - - name: Resolve release tag - id: tag - run: | - TAG="${{ github.event.inputs.release_tag }}" - [ -z "$TAG" ] && TAG="${GITHUB_REF_NAME}" - echo "tag=$TAG" >> "$GITHUB_OUTPUT" - # bare revision number for the release title: firefox-10 -> 10 - N="${TAG#firefox-}" - echo "num=$N" >> "$GITHUB_OUTPUT" - # previous release tag, for the changelog range (firefox-10 -> firefox-9) - case "$N" in (*[!0-9]*|'') echo "prevtag=" >> "$GITHUB_OUTPUT";; - (*) echo "prevtag=firefox-$((N-1))" >> "$GITHUB_OUTPUT";; esac - echo "publishing DRAFT release for tag: $TAG" - - name: Build release notes from the source commits - id: notes - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - set -e - CUR="$(cat src-meta/source-commit.txt 2>/dev/null | tr -d '[:space:]')" - echo "this build's source commit: ${CUR:-}" - # previous release's recorded source commit — gives the changelog range. - # Missing (first automated notes / firefox-0) -> notes omit the changelog. - PREV="" - PREVTAG="${{ steps.tag.outputs.prevtag }}" - if [ -n "$PREVTAG" ] && gh release download "$PREVTAG" -R "${{ github.repository }}" \ - --pattern source-commit.txt --dir prev 2>/dev/null; then - PREV="$(cat prev/source-commit.txt | tr -d '[:space:]')" - echo "previous ($PREVTAG) source commit: $PREV" - else - echo "no previous source-commit.txt — changelog section omitted this time" - fi - python scripts/gen_release_notes.py --tag "${{ steps.tag.outputs.tag }}" \ - --current "$CUR" --prev-sha "$PREV" --source-repo "${{ env.SOURCE_REPO }}" > body.md - echo "----- generated body.md -----"; cat body.md - # publish THIS build's source commit so the next release can diff from it - cp src-meta/source-commit.txt dl/source-commit.txt - - name: Create DRAFT release with all assets - uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2 - with: - tag_name: ${{ steps.tag.outputs.tag }} - name: firefox_antidetect_patch (150.0.1) rev ${{ steps.tag.outputs.num }} - draft: true - prerelease: false - fail_on_unmatched_files: true - files: | - dl/*.tar.gz - dl/*.zip - dl/checksums.txt - dl/source-commit.txt - body_path: body.md - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/republish.yml b/.github/workflows/republish.yml deleted file mode 100644 index 5595b57..0000000 --- a/.github/workflows/republish.yml +++ /dev/null @@ -1,214 +0,0 @@ -# ───────────────────────────────────────────────────────────────────────────── -# republish.yml — re-gate + publish an EXISTING build's artifacts WITHOUT rebuilding. -# -# When a release run's 5 builds all SUCCEEDED but the publish was blocked by a -# GATE/TEST bug (not a binary defect), rebuilding all five targets (hours) just to -# re-run a few-minute test is wasteful. This workflow downloads the binary artifacts -# from a prior `release` run, re-runs the (now-fixed) drive gate + cloak/WebGL guards -# against those exact byte-identical binaries on their native runners, and — only if -# green — publishes the DRAFT release. Same publish logic as release.yml (draft, notes, -# checksums, source-commit) so the human still runs the realness gate before un-drafting. -# -# Inputs: -# build_run_id : the `release` run whose `asset-*` + `source-commit` artifacts to reuse -# (must be within the 7-day artifact retention window). -# release_tag : tag to publish the draft under (e.g. firefox-11). -# ───────────────────────────────────────────────────────────────────────────── -name: republish - -on: - workflow_dispatch: - inputs: - build_run_id: - description: 'release run id whose built artifacts to reuse (no rebuild)' - required: true - release_tag: - description: 'release tag to publish the draft under (e.g. firefox-11)' - required: true - -env: - SOURCE_REPO: feder-cr/firefox_antidetect_patch - -jobs: - gate: - name: gate-${{ matrix.leg }} - runs-on: ${{ matrix.runner }} - timeout-minutes: 25 - permissions: - actions: read - contents: read - strategy: - fail-fast: false - matrix: - include: - - leg: linux-x86_64 - runner: ubuntu-24.04 - kind: linux - asset: firefox-150.0.1-stealth-linux-x86_64.tar.gz - extra: '--full' - - leg: linux-arm64 - runner: ubuntu-24.04-arm - kind: linux - asset: firefox-150.0.1-stealth-linux-arm64.tar.gz - extra: '' - - leg: win-x86_64 - runner: windows-latest - kind: win - asset: firefox-150.0.1-stealth-win-x86_64.zip - extra: '' - - leg: macos-arm64 - runner: macos-15 - kind: mac - asset: firefox-150.0.1-stealth-macos-arm64.tar.gz - extra: '' - - leg: macos-x86_64 - runner: macos-15-intel - kind: mac - asset: firefox-150.0.1-stealth-macos-x86_64.tar.gz - extra: '' - steps: - - name: Checkout wrapper (for scripts/ci_drive_gate.py — the FIXED gate) - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: { fetch-depth: 1 } - - name: Download asset from the existing build run (no rebuild) - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 - with: - name: asset-${{ matrix.leg }} - path: art - run-id: ${{ github.event.inputs.build_run_id }} - github-token: ${{ secrets.GITHUB_TOKEN }} - - name: Set up Python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 - with: { python-version: '3.11' } - - name: Install Playwright driver (pinned, no bundled browser) - shell: bash - run: python -m pip install --quiet "playwright==$(cat scripts/playwright_pin.txt)" - - name: Linux system deps for headless firefox - if: matrix.kind == 'linux' - run: sudo "$(which python)" -m playwright install-deps firefox - - name: Extract + locate firefox binary - shell: bash - run: | - set -e - mkdir -p ff - A="art/${{ matrix.asset }}" - case "${{ matrix.kind }}" in - win) python -c "import zipfile; zipfile.ZipFile('$A').extractall('ff')"; EXE="ff/firefox.exe";; - linux) tar xzf "$A" -C ff; EXE="ff/firefox";; - mac) tar xzf "$A" -C ff; EXE="ff/Firefox.app/Contents/MacOS/firefox";; - esac - [ -e "$EXE" ] || { echo "ERROR: firefox binary not found at $EXE"; exit 1; } - chmod +x "$EXE" 2>/dev/null || true - echo "FF_EXE=$EXE" >> "$GITHUB_ENV" - echo "located: $EXE" - - name: DRIVE GATE — Playwright launch via juggler + real page (+ interaction on --full) - shell: bash - run: python scripts/ci_drive_gate.py "$FF_EXE" ${{ matrix.extra }} - - name: Install pyobjc Quartz (macOS — to read the cloak window alpha) - if: matrix.kind == 'mac' - run: python -m pip install --quiet pyobjc-framework-Quartz - - name: Cloak + WebGL-masking guards (headed) - shell: bash - run: | - python -m pip install --quiet ".[dev]" - INVPW_BINARY_PATH="$FF_EXE" python -m pytest \ - tests/test_cloak.py \ - "tests/test_fingerprint_surface.py::test_webgl_readpixels_no_masking_signature" \ - -m e2e -o addopts='' -q - - publish: - name: publish-draft-release - needs: [gate] - runs-on: ubuntu-24.04 - permissions: - actions: read - contents: write - steps: - - name: Checkout wrapper (for scripts/gen_release_notes.py) - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: { fetch-depth: 1 } - - name: Set up Python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 - with: { python-version: '3.11' } - - name: Download all build assets from the existing build run (no rebuild) - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 - with: - pattern: asset-* - path: dl - merge-multiple: true - run-id: ${{ github.event.inputs.build_run_id }} - github-token: ${{ secrets.GITHUB_TOKEN }} - - name: Download source-commit metadata from the existing build run - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 - with: - name: source-commit - path: src-meta - run-id: ${{ github.event.inputs.build_run_id }} - github-token: ${{ secrets.GITHUB_TOKEN }} - - name: Assert all 5 target archives present (no silent partial release) - run: | - cd dl - EXPECTED=" - firefox-150.0.1-stealth-linux-x86_64.tar.gz - firefox-150.0.1-stealth-linux-arm64.tar.gz - firefox-150.0.1-stealth-win-x86_64.zip - firefox-150.0.1-stealth-macos-arm64.tar.gz - firefox-150.0.1-stealth-macos-x86_64.tar.gz - " - for a in $EXPECTED; do - [ -s "$a" ] || { echo "ERROR: missing/empty release asset: $a (a build leg silently dropped out?)"; exit 1; } - done - echo "all 5 target archives present" - - name: Generate checksums.txt - run: | - cd dl; ls -la - sha256sum firefox-150.0.1-stealth-* > checksums.txt - echo "----- checksums.txt -----"; cat checksums.txt - - name: Resolve release tag - id: tag - run: | - TAG="${{ github.event.inputs.release_tag }}" - echo "tag=$TAG" >> "$GITHUB_OUTPUT" - N="${TAG#firefox-}" - echo "num=$N" >> "$GITHUB_OUTPUT" - case "$N" in (*[!0-9]*|'') echo "prevtag=" >> "$GITHUB_OUTPUT";; - (*) echo "prevtag=firefox-$((N-1))" >> "$GITHUB_OUTPUT";; esac - echo "publishing DRAFT release for tag: $TAG" - - name: Build release notes from the source commits - id: notes - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - set -e - CUR="$(cat src-meta/source-commit.txt 2>/dev/null | tr -d '[:space:]')" - echo "this build's source commit: ${CUR:-}" - PREV="" - PREVTAG="${{ steps.tag.outputs.prevtag }}" - if [ -n "$PREVTAG" ] && gh release download "$PREVTAG" -R "${{ github.repository }}" \ - --pattern source-commit.txt --dir prev 2>/dev/null; then - PREV="$(cat prev/source-commit.txt | tr -d '[:space:]')" - echo "previous ($PREVTAG) source commit: $PREV" - else - echo "no previous source-commit.txt — changelog section omitted this time" - fi - python scripts/gen_release_notes.py --tag "${{ steps.tag.outputs.tag }}" \ - --current "$CUR" --prev-sha "$PREV" --source-repo "${{ env.SOURCE_REPO }}" > body.md - echo "----- generated body.md -----"; cat body.md - cp src-meta/source-commit.txt dl/source-commit.txt - - name: Create DRAFT release with all assets - uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2 - with: - tag_name: ${{ steps.tag.outputs.tag }} - name: firefox_antidetect_patch (150.0.1) rev ${{ steps.tag.outputs.num }} - draft: true - prerelease: false - fail_on_unmatched_files: true - files: | - dl/*.tar.gz - dl/*.zip - dl/checksums.txt - dl/source-commit.txt - body_path: body.md - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/verify-assets.yml b/.github/workflows/verify-assets.yml deleted file mode 100644 index b4d3567..0000000 --- a/.github/workflows/verify-assets.yml +++ /dev/null @@ -1,111 +0,0 @@ -# ───────────────────────────────────────────────────────────────────────────── -# verify-assets.yml — re-runnable DRIVE GATE for an EXISTING release's assets. -# -# release.yml drive-gates every binary it builds. This does the same drive test -# WITHOUT rebuilding: it downloads a release's already-published assets (works on -# DRAFT releases too via GITHUB_TOKEN) and drives each one on its native runner. -# -# Use it to: -# • drive-test a release that was built before the in-pipeline gate existed -# (e.g. firefox-9, built on the old release.yml), or -# • re-verify any shipped release on demand (regression check). -# -# Same single-source-of-truth drive logic as release.yml: scripts/ci_drive_gate.py. -# Headless, no screenshot → GPU-free. Zero proxy / zero secrets. -# ───────────────────────────────────────────────────────────────────────────── -name: verify-assets - -on: - workflow_dispatch: - inputs: - release_tag: - description: 'release tag whose assets to drive-test (e.g. firefox-9)' - required: true - -permissions: - # write (not read) is required: GitHub only exposes DRAFT releases to tokens - # with push access. With contents:read, `gh release download` on a draft tag - # 404s ("release not found"). This workflow only READS assets — the elevated - # scope is solely to make draft releases visible to GITHUB_TOKEN. - contents: write - -jobs: - drive: - name: drive-${{ matrix.leg }} - runs-on: ${{ matrix.runner }} - timeout-minutes: 25 - strategy: - fail-fast: false - matrix: - include: - # --full (interaction) only on the reliable linux-x86_64 leg; others run - # the robust SMOKE drive. Same rationale as release.yml's gate. - - leg: linux-x86_64 - runner: ubuntu-24.04 - kind: linux - asset: firefox-150.0.1-stealth-linux-x86_64.tar.gz - extra: '--full' - - leg: linux-arm64 - runner: ubuntu-24.04-arm - kind: linux - asset: firefox-150.0.1-stealth-linux-arm64.tar.gz - extra: '' - - leg: win-x86_64 - runner: windows-latest - kind: win - asset: firefox-150.0.1-stealth-win-x86_64.zip - extra: '' - - leg: macos-arm64 - runner: macos-15 - kind: mac - asset: firefox-150.0.1-stealth-macos-arm64.tar.gz - extra: '' - - leg: macos-x86_64 - runner: macos-15-intel - kind: mac - asset: firefox-150.0.1-stealth-macos-x86_64.tar.gz - extra: '' - steps: - - name: Checkout wrapper (for scripts/ci_drive_gate.py) - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: { fetch-depth: 1 } - - name: Download the release asset (draft releases included) - shell: bash - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - set -e - mkdir -p art - gh release download "${{ github.event.inputs.release_tag }}" \ - --repo "${{ github.repository }}" \ - --pattern "${{ matrix.asset }}" \ - --dir art - ls -la art/ - - name: Set up Python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 - with: { python-version: '3.11' } - - name: Install Playwright driver (no bundled browser — we override executable_path) - # Single-source pin (see release.yml); the drive gate enforces juggler compat. - shell: bash - run: python -m pip install --quiet "playwright==$(cat scripts/playwright_pin.txt)" - - name: Linux system deps for headless firefox - if: matrix.kind == 'linux' - run: sudo "$(which python)" -m playwright install-deps firefox - - name: Extract + locate firefox binary - shell: bash - run: | - set -e - mkdir -p ff - A="art/${{ matrix.asset }}" - case "${{ matrix.kind }}" in - win) python -c "import zipfile; zipfile.ZipFile('$A').extractall('ff')"; EXE="ff/firefox.exe";; - linux) tar xzf "$A" -C ff; EXE="ff/firefox";; - mac) tar xzf "$A" -C ff; EXE="ff/Firefox.app/Contents/MacOS/firefox";; - esac - [ -e "$EXE" ] || { echo "ERROR: firefox binary not found at $EXE"; exit 1; } - chmod +x "$EXE" 2>/dev/null || true - echo "FF_EXE=$EXE" >> "$GITHUB_ENV" - echo "located: $EXE" - - name: DRIVE GATE — Playwright launch via juggler + real page (+ interaction on --full) - shell: bash - run: python scripts/ci_drive_gate.py "$FF_EXE" ${{ matrix.extra }} diff --git a/.github/workflows/verify-cloak.yml b/.github/workflows/verify-cloak.yml deleted file mode 100644 index 4d119e8..0000000 --- a/.github/workflows/verify-cloak.yml +++ /dev/null @@ -1,103 +0,0 @@ -# ───────────────────────────────────────────────────────────────────────────── -# verify-cloak.yml — re-runnable CLOAK + WEBGL-MASKING GUARDS for an EXISTING -# build run's artifacts, WITHOUT rebuilding Firefox (~3h on the mac legs). -# -# release.yml runs these same guards in its `gate` job against each freshly-built -# artifact. This re-runs them against the artifacts of a PRIOR build run (input -# `run_id`) using the CURRENT wrapper code on the default branch — so a test-only -# fix (e.g. making the macOS leg tolerant of the runner's missing WebGL) can be -# validated against the real binaries in ~10 min instead of paying a full rebuild. -# -# Same guard command as release.yml's gate. Headed-but-cloaked; zero proxy / zero -# secrets. The macOS legs are the only place the cocoa cloak patch actually RUNS. -# ───────────────────────────────────────────────────────────────────────────── -name: verify-cloak - -on: - workflow_dispatch: - inputs: - run_id: - description: 'build run id whose asset-* artifacts to re-gate (e.g. 27346856197)' - required: true - -permissions: - contents: read - actions: read # download-artifact needs this to read another run's artifacts - -jobs: - guard: - name: guard-${{ matrix.leg }} - runs-on: ${{ matrix.runner }} - timeout-minutes: 25 - strategy: - fail-fast: false - matrix: - # Same legs/runners/assets as release.yml's gate matrix. - include: - - leg: linux-x86_64 - runner: ubuntu-24.04 - kind: linux - asset: firefox-150.0.1-stealth-linux-x86_64.tar.gz - - leg: linux-arm64 - runner: ubuntu-24.04-arm - kind: linux - asset: firefox-150.0.1-stealth-linux-arm64.tar.gz - - leg: win-x86_64 - runner: windows-latest - kind: win - asset: firefox-150.0.1-stealth-win-x86_64.zip - - leg: macos-arm64 - runner: macos-15 - kind: mac - asset: firefox-150.0.1-stealth-macos-arm64.tar.gz - - leg: macos-x86_64 - runner: macos-15-intel - kind: mac - asset: firefox-150.0.1-stealth-macos-x86_64.tar.gz - steps: - - name: Checkout wrapper (current default branch — the FIXED tests) - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: { fetch-depth: 1 } - - name: Download build asset from the prior run (no rebuild) - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 - with: - name: asset-${{ matrix.leg }} - path: art - run-id: ${{ github.event.inputs.run_id }} - github-token: ${{ secrets.GITHUB_TOKEN }} - - name: Set up Python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 - with: { python-version: '3.11' } - - name: Install Playwright driver (no bundled browser — we override executable_path) - # Single-source pin (see release.yml); the wrapper enforces juggler compat. - shell: bash - run: python -m pip install --quiet "playwright==$(cat scripts/playwright_pin.txt)" - - name: Linux system deps for headless firefox - if: matrix.kind == 'linux' - run: sudo "$(which python)" -m playwright install-deps firefox - - name: Extract + locate firefox binary - shell: bash - run: | - set -e - mkdir -p ff - A="art/${{ matrix.asset }}" - case "${{ matrix.kind }}" in - win) python -c "import zipfile; zipfile.ZipFile('$A').extractall('ff')"; EXE="ff/firefox.exe";; - linux) tar xzf "$A" -C ff; EXE="ff/firefox";; - mac) tar xzf "$A" -C ff; EXE="ff/Firefox.app/Contents/MacOS/firefox";; - esac - [ -e "$EXE" ] || { echo "ERROR: firefox binary not found at $EXE"; exit 1; } - chmod +x "$EXE" 2>/dev/null || true - echo "FF_EXE=$EXE" >> "$GITHUB_ENV" - echo "located: $EXE" - - name: Install pyobjc Quartz (macOS — to read the cloak window alpha) - if: matrix.kind == 'mac' - run: python -m pip install --quiet pyobjc-framework-Quartz - - name: Cloak + WebGL-masking guards (headed) - shell: bash - run: | - python -m pip install --quiet ".[dev]" - INVPW_BINARY_PATH="$FF_EXE" python -m pytest \ - tests/test_cloak.py \ - "tests/test_fingerprint_surface.py::test_webgl_readpixels_no_masking_signature" \ - -m e2e -o addopts='' -q