fix(tun): drive tun2socks through its YAML config #586
Workflow file for this run
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: CI | |
| on: | |
| pull_request: | |
| push: | |
| branches: [main] | |
| jobs: | |
| # ── One cheap job computes which areas a PR touches; the heavy jobs gate on its | |
| # outputs (`needs` + job-level `if`), so an irrelevant area's job never starts | |
| # (no runner spun up — notably the Windows one). A skipped job doesn't satisfy a | |
| # required status check, so the single required check is the `ci` aggregator at | |
| # the bottom, not the individual jobs. ── | |
| changes: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| frontend: ${{ steps.detect.outputs.frontend }} | |
| rust: ${{ steps.detect.outputs.rust }} | |
| desktop: ${{ steps.detect.outputs.desktop }} | |
| nix: ${{ steps.detect.outputs.nix }} | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Detect changes | |
| id: detect | |
| run: | | |
| if [ "${{ github.event_name }}" = "pull_request" ]; then | |
| base="${{ github.event.pull_request.base.sha }}" | |
| head="${{ github.event.pull_request.head.sha }}" | |
| else | |
| base="${{ github.event.before }}" | |
| head="${{ github.sha }}" | |
| fi | |
| # No usable base (first push, force-push, shallow clone) → run everything. | |
| if [ -z "$base" ] || ! git cat-file -e "$base" 2>/dev/null; then | |
| { | |
| echo "frontend=true" | |
| echo "rust=true" | |
| echo "desktop=true" | |
| echo "nix=true" | |
| } >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| # Three-dot: diff from the merge-base, not base..head directly. When the | |
| # branch is behind the base (main advanced after it forked — the common | |
| # case), a two-dot diff would also list every file main changed since, | |
| # falsely flagging areas the PR never touched. This mirrors GitHub's own | |
| # PR diff semantics. | |
| files=$(git diff --name-only "$base...$head") | |
| match() { printf '%s\n' "$files" | grep -Eq "$1"; } | |
| fe=false; rs=false; dk=false; nx=false | |
| match '^(frontend/|package\.json|bun\.lock|biome\.json)' && fe=true | |
| match '^(crates/|src-tauri/|Cargo\.(toml|lock)|flake\.(nix|lock))' && rs=true | |
| match '^(crates/|src-tauri/|frontend/|Cargo\.(toml|lock))' && dk=true | |
| match '^(nix/|flake\.(nix|lock))' && nx=true | |
| { | |
| echo "frontend=$fe" | |
| echo "rust=$rs" | |
| echo "desktop=$dk" | |
| echo "nix=$nx" | |
| } >> "$GITHUB_OUTPUT" | |
| # ── Frontend: bun workspace (lockfile at the repo root), build, tests, i18n. | |
| # Formatting/lint (biome) is covered by the `treefmt` job, not here. ── | |
| frontend: | |
| needs: changes | |
| if: needs.changes.outputs.frontend == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 | |
| with: | |
| bun-version: latest | |
| - name: Install dependencies | |
| # The bun workspace is rooted at the repo; install from the root so the | |
| # frontend member and its deps resolve against the single bun.lock. | |
| run: bun install --frozen-lockfile | |
| - name: Build (tsc + vite) | |
| working-directory: frontend | |
| run: bun run build | |
| - name: Unit tests | |
| working-directory: frontend | |
| run: bunx vitest run | |
| - name: i18n dictionaries | |
| run: bun run frontend/scripts/check-i18n.ts | |
| # ── Rust workspace: clippy, tests, and generated-bindings drift, all in | |
| # the flake devshell (carries the rust toolchain + webkit for src-tauri). ── | |
| rust: | |
| needs: changes | |
| if: needs.changes.outputs.rust == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Install Nix | |
| uses: DeterminateSystems/nix-installer-action@da36cb69b1c3247ad7a1f931ebfd954a1105ef14 # v14 | |
| with: | |
| extra-conf: | | |
| accept-flake-config = true | |
| # ── Nix store cache (cachix). Pulls the devshell closure from our public | |
| # cache as a substituter and pushes anything newly built at the end. Reads | |
| # need no token (public cache), so fork PRs benefit too; pushes use the | |
| # CACHIX_AUTH_TOKEN secret (absent on forks → read-only there). Replaces the | |
| # retired magic-nix-cache, whose GHA backend was a no-op. ── | |
| - name: Cachix | |
| uses: cachix/cachix-action@5f2d7c5294214f71b873db4b969586b980625e71 # v17 | |
| with: | |
| name: kasumi-proxy | |
| authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} | |
| # ── Cargo registry — dependency sources only, no compiled artifacts, so it's | |
| # portable across toolchain bumps and keyed on Cargo.lock alone. ── | |
| - name: Cache cargo registry | |
| uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 | |
| with: | |
| path: | | |
| ~/.cargo/registry/index | |
| ~/.cargo/registry/cache | |
| ~/.cargo/git/db | |
| key: cargo-registry-${{ runner.os }}-${{ hashFiles('Cargo.lock') }} | |
| restore-keys: | | |
| cargo-registry-${{ runner.os }}- | |
| # ── Workspace build artifacts (./target). The compile of clippy --all-targets + | |
| # test + codegen (hundreds of crates: tauri, specta, webkit bindings) is the | |
| # bulk of this job; an empty target means a full rebuild every run. But the | |
| # build-script binaries under target are linked against the devshell's glibc, | |
| # so a target restored from a different flake.lock points its ELF interpreter | |
| # at a /nix/store loader that's no longer there and fails to exec ("No such | |
| # file or directory"). So the key AND the restore fallback are both bound to | |
| # flake.lock: a Cargo.lock-only bump still reuses target (only the changed | |
| # crates recompile), but a flake.lock/toolchain bump starts clean instead of | |
| # restoring a poisoned target. The devshell sets no CARGO_HOME / | |
| # CARGO_TARGET_DIR, so cargo uses ~/.cargo and ./target on the host. ── | |
| - name: Cache cargo build | |
| uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 | |
| with: | |
| path: target | |
| key: cargo-target-${{ runner.os }}-${{ hashFiles('flake.lock') }}-${{ hashFiles('Cargo.lock') }} | |
| restore-keys: | | |
| cargo-target-${{ runner.os }}-${{ hashFiles('flake.lock') }}- | |
| - name: Stub the libcronet bundle resource (clippy/codegen build the app) | |
| run: | | |
| # clippy/codegen compile kasumi-desktop, whose tauri.linux.conf.json | |
| # declares the libcronet.so resource; tauri-build checks the path exists. | |
| # The real lib is fetched only for the bundle — a placeholder suffices. | |
| mkdir -p src-tauri/binaries | |
| : > src-tauri/binaries/libcronet.so | |
| - name: clippy + test + codegen drift | |
| run: | | |
| nix develop --command bash -euo pipefail -c ' | |
| cargo clippy --workspace --all-targets -- -D warnings | |
| cargo test --workspace | |
| # The generated frontend bindings/schemas/defaults must match the Rust | |
| # source — regenerate and fail on any diff. | |
| cargo run -p kasumi-desktop --bin codegen | |
| git diff --exit-code -- frontend/src/generated | |
| ' | |
| # ── Realise the desktop package derivation. The `rust` job only runs cargo in the | |
| # devshell, never `nix build .#kasumi-desktop`, so installPhase / wrapper / icon | |
| # staging were unchecked on PRs. Path-gated on the nix expressions. ── | |
| nix-build: | |
| needs: changes | |
| if: needs.changes.outputs.nix == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Install Nix | |
| uses: DeterminateSystems/nix-installer-action@da36cb69b1c3247ad7a1f931ebfd954a1105ef14 # v14 | |
| with: | |
| extra-conf: | | |
| accept-flake-config = true | |
| - name: Cachix | |
| uses: cachix/cachix-action@5f2d7c5294214f71b873db4b969586b980625e71 # v17 | |
| with: | |
| name: kasumi-proxy | |
| authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} | |
| - name: Build the desktop package | |
| run: nix build .#kasumi-desktop --accept-flake-config --print-build-logs | |
| # ── The formatting gate for the whole tree — the single source of truth. This | |
| # realises the flake's own treefmt check derivation (the exact `nix fmt` config, | |
| # no duplicated formatter list), which runs rustfmt, biome (format + lint), | |
| # shellcheck, and the nix formatters (alejandra/statix/deadnix) over every tracked | |
| # file. Because it covers all of them, the per-area jobs no longer re-run | |
| # `cargo fmt` / `biome` / `shellcheck`. Ungated on purpose: it runs on every PR so | |
| # no area can slip an unformatted file past a path filter (a Rust-only or | |
| # scripts-only change used to skip the comprehensive check entirely). ── | |
| treefmt: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Install Nix | |
| uses: DeterminateSystems/nix-installer-action@da36cb69b1c3247ad7a1f931ebfd954a1105ef14 # v14 | |
| with: | |
| extra-conf: | | |
| accept-flake-config = true | |
| - name: Cachix | |
| uses: cachix/cachix-action@5f2d7c5294214f71b873db4b969586b980625e71 # v17 | |
| with: | |
| name: kasumi-proxy | |
| authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} | |
| - name: treefmt check (fails on any unformatted file) | |
| run: nix build .#checks.x86_64-linux.treefmt --accept-flake-config --print-build-logs | |
| # ── Desktop compile smoke (Linux + Windows) on the plain rustup toolchain — the | |
| # path release/nightly actually bundle with (the nix `nix-build` job realises the | |
| # flake package; this proves the non-nix one). Compile-only (no bundle): catches | |
| # platform/linker breakage without the slow installer. macOS is still deferred | |
| # (no Platform port yet). ── | |
| desktop-linux: | |
| needs: changes | |
| if: needs.changes.outputs.desktop == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Install Nix | |
| uses: DeterminateSystems/nix-installer-action@da36cb69b1c3247ad7a1f931ebfd954a1105ef14 # v14 | |
| with: | |
| extra-conf: | | |
| accept-flake-config = true | |
| - name: Cachix | |
| uses: cachix/cachix-action@5f2d7c5294214f71b873db4b969586b980625e71 # v17 | |
| with: | |
| name: kasumi-proxy | |
| authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} | |
| - name: Stub the libcronet bundle resource (compile-smoke doesn't bundle) | |
| run: | | |
| mkdir -p src-tauri/binaries | |
| : > src-tauri/binaries/libcronet.so | |
| - name: Compile the Tauri app (no bundle) | |
| run: | | |
| nix develop --command bash -euo pipefail -c ' | |
| bun install --frozen-lockfile | |
| ( cd frontend && bun run build ) | |
| cargo build -p kasumi-desktop | |
| ' | |
| # ── Windows desktop compile smoke. Mirrors desktop-linux: the `rust` job runs on | |
| # Linux and never compiles the #[cfg(windows)] Platform + Win32 code, so this is | |
| # what catches a Windows-build break in the PR instead of the next nightly. | |
| # (The Android daemon needs no such job: it has no cfg(target_os) gates — just | |
| # portable Rust shelling out to ip/iptables — so the `rust` workspace job already | |
| # compiles it on every PR. Only the rarer NDK cross-link is left to nightly.) ── | |
| desktop-windows: | |
| needs: changes | |
| if: needs.changes.outputs.desktop == 'true' | |
| runs-on: windows-latest | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Rust | |
| uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable | |
| with: | |
| toolchain: stable | |
| targets: x86_64-pc-windows-msvc | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 | |
| with: | |
| bun-version: latest | |
| - name: Build the UI (generate_context embeds frontendDist) | |
| shell: bash | |
| run: | | |
| bun install --frozen-lockfile | |
| ( cd frontend && bun run build ) | |
| - name: Stub the bundle resources (compile-smoke doesn't bundle) | |
| shell: bash | |
| run: | | |
| # tauri-build validates declared bundle.resources at compile time. The | |
| # real wintun.dll / libcronet.dll / msys-2.0.dll are fetched only for the | |
| # nightly/release bundle; placeholders satisfy the path check for this | |
| # compile-only job. | |
| mkdir -p src-tauri/binaries | |
| : > src-tauri/binaries/wintun.dll | |
| : > src-tauri/binaries/libcronet.dll | |
| : > src-tauri/binaries/msys-2.0.dll | |
| - name: Compile the Tauri app (no bundle) | |
| run: cargo build -p kasumi-desktop | |
| # ── The single required status check. It always runs and aggregates the gated | |
| # jobs above: a skipped area (its files weren't touched) is fine; only a real | |
| # failure or cancellation fails CI. Point branch protection at THIS context so | |
| # path-skipped jobs never block a merge. ── | |
| ci: | |
| needs: [changes, frontend, rust, nix-build, treefmt, desktop-linux, desktop-windows] | |
| if: always() | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Verify no required job failed | |
| run: | | |
| results="${{ join(needs.*.result, ' ') }}" | |
| echo "job results: $results" | |
| for r in $results; do | |
| if [ "$r" = "failure" ] || [ "$r" = "cancelled" ]; then | |
| echo "a CI job did not pass" | |
| exit 1 | |
| fi | |
| done | |
| echo "all CI jobs passed or were skipped" |