diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 918584b..2753baf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,80 +1,81 @@ -name: Build Rust Application - +name: Build Rust Application + on: push: branches: [ master ] + tags: [ 'v*.*.*' ] pull_request: workflow_dispatch: - -env: - CARGO_TERM_COLOR: always - -jobs: - linux-amd64: - name: Linux amd64 (.deb) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - name: Install Rust - uses: dtolnay/rust-toolchain@stable - - - name: Install cargo-deb - run: cargo install cargo-deb - - - name: Build Debian package - run: cargo deb -- --bin cheburchecker - - - name: Upload artifact - uses: actions/upload-artifact@v7 - with: - name: linux-amd64-deb - path: target/debian/*.deb - compression-level: 0 - - linux-arm64: - name: Linux arm64 (.deb via cross) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - name: Install Rust - uses: dtolnay/rust-toolchain@stable - - - name: Install cargo-deb - run: cargo install cargo-deb - - - name: Build binary - uses: houseabsolute/actions-rust-cross@v1 - with: - command: build - target: "aarch64-unknown-linux-gnu" - args: "--release --bin cheburchecker" - strip: true - - - name: Create Debian archive - run: cargo deb --target aarch64-unknown-linux-gnu --no-strip --no-build -- --bin cheburchecker - - - name: Upload artifact - uses: actions/upload-artifact@v7 - with: - name: linux-arm64-deb - path: target/aarch64-unknown-linux-gnu/debian/*.deb - - windows: - name: Windows amd64 - runs-on: windows-latest - steps: - - uses: actions/checkout@v6 - - - name: Install Rust - uses: dtolnay/rust-toolchain@stable - - - name: Build release binary - run: cargo build --release --bin cheburchecker - - - name: Upload artifact - uses: actions/upload-artifact@v7 - with: - name: windows-amd64-exe - path: target/release/*.exe + +env: + CARGO_TERM_COLOR: always + +jobs: + linux-amd64: + name: Linux amd64 (.deb) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + + - name: Install cargo-deb + run: cargo install cargo-deb + + - name: Build Debian package + run: cargo deb -p reporter -- --bin cheburchecker + + - name: Upload artifact + uses: actions/upload-artifact@v7 + with: + name: linux-amd64-deb + path: target/debian/*.deb + compression-level: 0 + + linux-arm64: + name: Linux arm64 (.deb via cross) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + + - name: Install cargo-deb + run: cargo install cargo-deb + + - name: Build binary + uses: houseabsolute/actions-rust-cross@v1 + with: + command: build + target: "aarch64-unknown-linux-gnu" + args: "--release --bin cheburchecker" + strip: true + + - name: Create Debian archive + run: cargo deb --target aarch64-unknown-linux-gnu --no-strip --no-build -p reporter -- --bin cheburchecker + + - name: Upload artifact + uses: actions/upload-artifact@v7 + with: + name: linux-arm64-deb + path: target/aarch64-unknown-linux-gnu/debian/*.deb + + windows: + name: Windows amd64 + runs-on: windows-latest + steps: + - uses: actions/checkout@v6 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + + - name: Build release binary + run: cargo build --release --bin cheburchecker + + - name: Upload artifact + uses: actions/upload-artifact@v7 + with: + name: windows-amd64-exe + path: target/release/*.exe diff --git a/.github/workflows/probe-build.yml b/.github/workflows/probe-build.yml new file mode 100644 index 0000000..bab0a33 --- /dev/null +++ b/.github/workflows/probe-build.yml @@ -0,0 +1,183 @@ +name: Build Probe + +on: + push: + branches: [ master ] + tags: [ 'v*.*.*' ] + pull_request: + workflow_dispatch: + +env: + CARGO_TERM_COLOR: always + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }}-probe + +jobs: + linux-amd64: + name: Linux amd64 binary and .deb + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + + - name: Install cargo-deb + run: cargo install cargo-deb + + - name: Build release binary + run: cargo build --release --package probe --bin cheburprobe + + - name: Build Debian package + run: cargo deb --package probe --no-build -- --bin cheburprobe + + - name: Upload Linux amd64 binary + uses: actions/upload-artifact@v7 + with: + name: cheburprobe-linux-amd64 + path: target/release/cheburprobe + compression-level: 0 + + - name: Upload Linux amd64 Debian package + uses: actions/upload-artifact@v7 + with: + name: cheburprobe-linux-amd64-deb + path: target/debian/*.deb + compression-level: 0 + + linux-arm64: + name: Linux arm64 binary and .deb + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + + - name: Install cargo-deb + run: cargo install cargo-deb + + - name: Build release binary + uses: houseabsolute/actions-rust-cross@v1 + with: + command: build + target: "aarch64-unknown-linux-gnu" + args: "--release --package probe --bin cheburprobe" + strip: true + + - name: Build Debian package + run: cargo deb --package probe --target aarch64-unknown-linux-gnu --no-strip --no-build -- --bin cheburprobe + + - name: Upload Linux arm64 binary + uses: actions/upload-artifact@v7 + with: + name: cheburprobe-linux-arm64 + path: target/aarch64-unknown-linux-gnu/release/cheburprobe + compression-level: 0 + + - name: Upload Linux arm64 Debian package + uses: actions/upload-artifact@v7 + with: + name: cheburprobe-linux-arm64-deb + path: target/aarch64-unknown-linux-gnu/debian/*.deb + compression-level: 0 + + windows-amd64: + name: Windows amd64 binary + runs-on: windows-latest + steps: + - uses: actions/checkout@v6 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + + - name: Build release binary + run: cargo build --release --package probe --bin cheburprobe + + - name: Upload Windows amd64 binary + uses: actions/upload-artifact@v7 + with: + name: cheburprobe-windows-amd64 + path: target/release/cheburprobe.exe + compression-level: 0 + + docker: + name: Docker image amd64/arm64 + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + id-token: write + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v4 + + - name: Set up Docker Buildx + id: setup-buildx + uses: docker/setup-buildx-action@v4 + + - name: Cache Rust Docker build mounts + id: rust-build-cache + uses: actions/cache@v4 + with: + path: rust-build-cache + key: ${{ runner.os }}-probe-docker-rust-${{ hashFiles('**/Cargo.lock', 'probe/Dockerfile') }} + restore-keys: | + ${{ runner.os }}-probe-docker-rust- + + - name: Restore Rust Docker build cache + uses: reproducible-containers/buildkit-cache-dance@v3 + with: + builder: ${{ steps.setup-buildx.outputs.name }} + cache-map: | + { + "rust-build-cache/probe-target": { + "target": "/build/target", + "id": "probe-target" + }, + "rust-build-cache/cargo-registry": { + "target": "/usr/local/cargo/registry", + "id": "cargo-registry" + }, + "rust-build-cache/cargo-git": { + "target": "/usr/local/cargo/git", + "id": "cargo-git" + } + } + skip-extraction: ${{ steps.rust-build-cache.outputs.cache-hit }} + + - name: Log into registry ${{ env.REGISTRY }} + if: github.event_name != 'pull_request' + uses: docker/login-action@v4 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v6 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=sha + type=raw,value=latest,enable={{is_default_branch}} + + - name: Build and push Docker image + uses: docker/build-push-action@v7 + with: + context: . + file: ./probe/Dockerfile + platforms: linux/amd64,linux/arm64 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..3dac4df --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,154 @@ +name: Draft Release + +on: + push: + tags: [ 'v*.*.*' ] + +permissions: + actions: read + contents: write + +concurrency: + group: draft-release-${{ github.ref_name }} + cancel-in-progress: false + +jobs: + draft-release: + name: Draft GitHub release + runs-on: ubuntu-latest + steps: + - name: Collect build artifacts + uses: actions/github-script@v8 + env: + TAG_NAME: ${{ github.ref_name }} + WORKFLOWS: | + Build Rust Application + Build Probe + with: + script: | + const fs = require('fs'); + const path = require('path'); + + const owner = context.repo.owner; + const repo = context.repo.repo; + const tagName = process.env.TAG_NAME; + const workflows = process.env.WORKFLOWS + .split(/\r?\n/) + .map((name) => name.trim()) + .filter(Boolean); + const targetSha = context.sha; + const outDir = path.join(process.cwd(), 'artifact-archives'); + const pollDelayMs = 30_000; + const timeoutMs = 30 * 60 * 1000; + + fs.mkdirSync(outDir, { recursive: true }); + + async function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); + } + + async function findWorkflowRun(workflowName) { + const startedAt = Date.now(); + + while (Date.now() - startedAt < timeoutMs) { + const runs = await github.paginate( + github.rest.actions.listWorkflowRunsForRepo, + { + owner, + repo, + head_sha: targetSha, + event: 'push', + per_page: 100, + }, + ); + + const run = runs.find((candidate) => + candidate.name === workflowName && + candidate.head_sha === targetSha && + candidate.head_branch === tagName + ); + + if (run?.status === 'completed') { + if (run.conclusion === 'success') { + return run; + } + + throw new Error( + `${workflowName} completed with conclusion ${run.conclusion}: ${run.html_url}`, + ); + } + + core.info( + run + ? `${workflowName} is ${run.status}; waiting for completion.` + : `Waiting for ${workflowName} to start for ${tagName}.`, + ); + await sleep(pollDelayMs); + } + + throw new Error(`Timed out waiting for ${workflowName} on ${tagName}.`); + } + + for (const workflowName of workflows) { + const run = await findWorkflowRun(workflowName); + core.info(`Downloading artifacts from ${workflowName}: ${run.html_url}`); + + const artifacts = await github.paginate( + github.rest.actions.listWorkflowRunArtifacts, + { + owner, + repo, + run_id: run.id, + per_page: 100, + }, + ); + + for (const artifact of artifacts.filter((item) => !item.expired)) { + const archive = await github.rest.actions.downloadArtifact({ + owner, + repo, + artifact_id: artifact.id, + archive_format: 'zip', + }); + + const artifactPath = path.join(outDir, `${artifact.name}.zip`); + fs.writeFileSync(artifactPath, Buffer.from(archive.data)); + core.info(`Saved ${artifactPath}`); + } + } + + - name: Extract release assets + shell: bash + run: | + set -euo pipefail + + mkdir -p release-artifacts + + for archive in artifact-archives/*.zip; do + artifact_name="$(basename "$archive" .zip)" + extract_dir="$(mktemp -d)" + + unzip -q "$archive" -d "$extract_dir" + + while IFS= read -r -d '' file; do + filename="$(basename "$file")" + target="release-artifacts/$filename" + + if [ -e "$target" ]; then + target="release-artifacts/${artifact_name}-${filename}" + fi + + mv "$file" "$target" + done < <(find "$extract_dir" -type f -print0) + + rm -rf "$extract_dir" + done + + - name: Publish draft release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ github.ref_name }} + name: ${{ github.ref_name }} + draft: true + fail_on_unmatched_files: true + files: release-artifacts/* diff --git a/Cargo.lock b/Cargo.lock index 994a82d..136e11f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -130,6 +130,38 @@ dependencies = [ "syn", ] +[[package]] +name = "async-tungstenite" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef0f7efedeac57d9b26170f72965ecfd31473ca52ca7a64e925b0b6f5f079886" +dependencies = [ + "atomic-waker", + "futures-core", + "futures-io", + "futures-task", + "futures-util", + "log", + "pin-project-lite", + "rustls-native-certs 0.8.4", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.4", + "tungstenite", +] + +[[package]] +name = "async_io_stream" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" +dependencies = [ + "futures", + "pharos", + "rustc_version", + "tokio", +] + [[package]] name = "atoi" version = "2.0.0" @@ -166,6 +198,28 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-lc-rs" +version = "1.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a054912289d18629dc78375ba2c3726a3afe3ff71b4edba9dedfca0e3446d1fc" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa7e52a4c5c547c741610a2c6f123f3881e409b714cd27e6798ef020c514f0a" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + [[package]] name = "base64" version = "0.22.1" @@ -192,9 +246,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.10.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "84d7ced0ae9557296835c32bf1b1e02b44c746701f898460fb000d7eaa84f00a" dependencies = [ "serde_core", ] @@ -249,6 +303,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd405d82c84ff7f35739f175f67d8b9fb7687a0e84ccdc78bd3568839827cf07" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] @@ -351,6 +407,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + [[package]] name = "colorchoice" version = "1.0.4" @@ -416,6 +481,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -615,7 +690,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b035a542cf7abf01f2e3c4d5a7acbaebfefe120ae4efc7bde3df98186e4b8af7" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.12.1", "proc-macro2", "proc-macro2-diagnostics", "quote", @@ -651,6 +726,12 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "either" version = "1.15.0" @@ -774,6 +855,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + [[package]] name = "flate2" version = "1.1.9" @@ -831,6 +918,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "fsevent-sys" version = "4.1.0" @@ -1034,7 +1127,7 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.12.1", "ignore", "walkdir", ] @@ -1167,11 +1260,11 @@ dependencies = [ "ipnet", "jni", "rand 0.10.0", - "rustls", - "thiserror", + "rustls 0.23.35", + "thiserror 2.0.18", "tinyvec", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.4", "tracing", "url", "webpki-roots", @@ -1191,7 +1284,7 @@ dependencies = [ "prefix-trie 0.8.2", "rand 0.10.0", "ring", - "thiserror", + "thiserror 2.0.18", "tinyvec", "tracing", "url", @@ -1216,12 +1309,12 @@ dependencies = [ "parking_lot", "rand 0.10.0", "resolv-conf", - "rustls", + "rustls 0.23.35", "smallvec", "system-configuration 0.7.0", - "thiserror", + "thiserror 2.0.18", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.4", "tracing", "webpki-roots", ] @@ -1384,10 +1477,10 @@ dependencies = [ "http 1.4.0", "hyper 1.8.1", "hyper-util", - "rustls", + "rustls 0.23.35", "rustls-pki-types", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.4", "tower-service", "webpki-roots", ] @@ -1739,7 +1832,7 @@ dependencies = [ "jni-sys", "log", "simd_cesu8", - "thiserror", + "thiserror 2.0.18", "walkdir", "windows-link", ] @@ -1776,6 +1869,16 @@ dependencies = [ "syn", ] +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + [[package]] name = "js-sys" version = "0.3.82" @@ -1839,7 +1942,7 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.12.1", "libc", "redox_syscall", ] @@ -1921,7 +2024,7 @@ dependencies = [ "log", "memchr", "serde", - "thiserror", + "thiserror 2.0.18", ] [[package]] @@ -2025,10 +2128,10 @@ dependencies = [ "libc", "log", "openssl", - "openssl-probe", + "openssl-probe 0.1.6", "openssl-sys", "schannel", - "security-framework", + "security-framework 2.11.1", "security-framework-sys", "tempfile", ] @@ -2066,7 +2169,7 @@ version = "6.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.12.1", "crossbeam-channel", "filetime", "fsevent-sys", @@ -2172,7 +2275,7 @@ version = "0.10.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.12.1", "cfg-if", "foreign-types", "libc", @@ -2198,6 +2301,12 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + [[package]] name = "openssl-sys" version = "0.9.111" @@ -2335,6 +2444,16 @@ dependencies = [ "sha2", ] +[[package]] +name = "pharos" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" +dependencies = [ + "futures", + "rustc_version", +] + [[package]] name = "phf" version = "0.11.3" @@ -2482,6 +2601,24 @@ dependencies = [ "syn", ] +[[package]] +name = "probe" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "env_logger", + "futures", + "log", + "reports", + "rumqttc 0.25.1", + "rustls 0.23.35", + "serde", + "serde_json", + "tokio", + "tokio-rustls 0.26.4", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -2537,7 +2674,7 @@ dependencies = [ "reqwest", "serde", "serde_json", - "thiserror", + "thiserror 2.0.18", "tokio", "url", ] @@ -2554,9 +2691,9 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls", + "rustls 0.23.35", "socket2 0.6.3", - "thiserror", + "thiserror 2.0.18", "tokio", "tracing", "web-time", @@ -2574,10 +2711,10 @@ dependencies = [ "rand 0.9.2", "ring", "rustc-hash", - "rustls", + "rustls 0.23.35", "rustls-pki-types", "slab", - "thiserror", + "thiserror 2.0.18", "tinyvec", "tracing", "web-time", @@ -2700,7 +2837,7 @@ version = "11.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.12.1", ] [[package]] @@ -2709,7 +2846,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.12.1", ] [[package]] @@ -2815,7 +2952,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls", + "rustls 0.23.35", "rustls-pki-types", "serde", "serde_json", @@ -2823,7 +2960,7 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-native-tls", - "tokio-rustls", + "tokio-rustls 0.26.4", "tokio-util", "tower", "tower-http", @@ -3011,6 +3148,48 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rumqttc" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1568e15fab2d546f940ed3a21f48bbbd1c494c90c99c4481339364a497f94a9" +dependencies = [ + "bytes", + "flume", + "futures-util", + "log", + "rustls-native-certs 0.7.3", + "rustls-pemfile", + "rustls-webpki 0.102.8", + "thiserror 1.0.69", + "tokio", + "tokio-rustls 0.25.0", +] + +[[package]] +name = "rumqttc" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0feff8d882bff0b2fddaf99355a10336d43dd3ed44204f85ece28cf9626ab519" +dependencies = [ + "async-tungstenite", + "bytes", + "fixedbitset", + "flume", + "futures-util", + "http 1.4.0", + "log", + "rustls-native-certs 0.8.4", + "rustls-pemfile", + "rustls-webpki 0.102.8", + "thiserror 2.0.18", + "tokio", + "tokio-rustls 0.26.4", + "tokio-stream", + "tokio-util", + "ws_stream_tungstenite", +] + [[package]] name = "rustc-hash" version = "2.1.1" @@ -3032,28 +3211,77 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.12.1", "errno", "libc", "linux-raw-sys", "windows-sys 0.61.2", ] +[[package]] +name = "rustls" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki 0.102.8", + "subtle", + "zeroize", +] + [[package]] name = "rustls" version = "0.23.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" dependencies = [ + "aws-lc-rs", "log", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki", + "rustls-webpki 0.103.8", "subtle", "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" +dependencies = [ + "openssl-probe 0.1.6", + "rustls-pemfile", + "rustls-pki-types", + "schannel", + "security-framework 2.11.1", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe 0.2.1", + "rustls-pki-types", + "schannel", + "security-framework 3.7.0", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "rustls-pki-types" version = "1.13.0" @@ -3064,12 +3292,24 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustls-webpki" version = "0.103.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -3123,8 +3363,21 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.10.0", - "core-foundation", + "bitflags 2.12.1", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.12.1", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -3132,9 +3385,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.15.0" +version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" dependencies = [ "core-foundation-sys", "libc", @@ -3408,7 +3661,7 @@ dependencies = [ "serde_json", "sha2", "smallvec", - "thiserror", + "thiserror 2.0.18", "tokio", "tokio-stream", "tracing", @@ -3462,7 +3715,7 @@ checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", "base64", - "bitflags 2.10.0", + "bitflags 2.12.1", "byteorder", "bytes", "chrono", @@ -3492,7 +3745,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror", + "thiserror 2.0.18", "tracing", "uuid", "whoami", @@ -3506,7 +3759,7 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64", - "bitflags 2.10.0", + "bitflags 2.12.1", "byteorder", "chrono", "crc", @@ -3531,7 +3784,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror", + "thiserror 2.0.18", "tracing", "uuid", "whoami", @@ -3557,7 +3810,7 @@ dependencies = [ "serde", "serde_urlencoded", "sqlx-core", - "thiserror", + "thiserror 2.0.18", "tracing", "url", "uuid", @@ -3647,8 +3900,8 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.10.0", - "core-foundation", + "bitflags 2.12.1", + "core-foundation 0.9.4", "system-configuration-sys", ] @@ -3658,8 +3911,8 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.10.0", - "core-foundation", + "bitflags 2.12.1", + "core-foundation 0.9.4", "system-configuration-sys", ] @@ -3725,13 +3978,33 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -3848,13 +4121,24 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls 0.22.4", + "rustls-pki-types", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls", + "rustls 0.23.35", "tokio", ] @@ -3944,7 +4228,7 @@ version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cf146f99d442e8e68e585f5d798ccd3cad9a7835b917e09728880a862706456" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.12.1", "bytes", "futures-util", "http 1.4.0", @@ -4036,6 +4320,25 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" +dependencies = [ + "bytes", + "data-encoding", + "http 1.4.0", + "httparse", + "log", + "rand 0.9.2", + "rustls 0.23.35", + "rustls-pki-types", + "sha1", + "thiserror 2.0.18", + "utf-8", +] + [[package]] name = "typenum" version = "1.19.0" @@ -4136,6 +4439,12 @@ dependencies = [ "serde", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -4325,7 +4634,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.12.1", "hashbrown 0.15.5", "indexmap", "semver", @@ -4362,7 +4671,7 @@ dependencies = [ [[package]] name = "website" -version = "1.0.0" +version = "1.1.0" dependencies = [ "dotenvy", "env_logger", @@ -4376,9 +4685,11 @@ dependencies = [ "rocket-cache-response", "rocket-client-addr", "rocket_dyn_templates", + "rumqttc 0.24.0", "serde", "sqlx", "tar", + "toml", ] [[package]] @@ -4821,7 +5132,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.10.0", + "bitflags 2.12.1", "indexmap", "log", "serde", @@ -4857,6 +5168,26 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +[[package]] +name = "ws_stream_tungstenite" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c9c55940d22313a53398bfeb9438c5f519de475fa37ed7ff068f8c1ca8eb45" +dependencies = [ + "async-tungstenite", + "async_io_stream", + "bitflags 2.12.1", + "futures-core", + "futures-io", + "futures-sink", + "futures-util", + "pharos", + "rustc_version", + "tokio", + "tracing", + "tungstenite", +] + [[package]] name = "xattr" version = "1.6.1" diff --git a/Cargo.toml b/Cargo.toml index 5b10cee..a9ee636 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ + "probe", "querying", "reporter", "reports", diff --git a/README.md b/README.md index f0ee41a..a9651d9 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,8 @@ - **Rust** — язык программирования, на котором написан весь бекенд. - **Rocket.rs** — веб-фреймворк для построения HTTP API и серверной логики. - **DoH резолвер Quad9** — для разрешения DNS-запросов используется DNS-over-HTTPS, что позволяет обойти локальные ограничения и получать актуальные данные. -- **SvelteKit** — SSR-фронтенд, который запускается отдельным Node.js сервисом. -- **nginx** — публичная точка входа, проксирует API в Rocket, а страницы в SvelteKit. +- **SvelteKit** — SSR-фронтенд, который запускается отдельным Node.js сервисом. +- **nginx** — публичная точка входа, проксирует API в Rocket, а страницы в SvelteKit. - **lucide** — библиотека иконок, применяемая для визуального оформления интерфейса. --- @@ -26,7 +26,7 @@ 1. Пользователь вводит домен или IP-адрес в форму на сайте. 2. Сервер выполняет DNS-запрос через Quad9 DoH для получения актуальной информации. 3. Полученный домен/IP проверяется на наличие в блокировочных списках через префиксные деревья. -4. Результат возвращается через API и отображается SSR-фронтендом SvelteKit. +4. Результат возвращается через API и отображается SSR-фронтендом SvelteKit. --- @@ -42,29 +42,30 @@ * `querying` — модуль проверки сайтов по базам данных * `reporter` — [Cheburcheck Reporter](reporter/README.md) -* `reports` — общий протокол для отправки отчетов -* `website` — Rocket API и серверная логика -* `frontend` — SvelteKit SSR-интерфейс - ---- - -## Запуск через Docker Compose - -```sh -docker compose up --build -``` - -По умолчанию nginx слушает `http://localhost:8080`. Порт можно изменить через -`HTTP_PORT`, например: - -```sh -HTTP_PORT=80 docker compose up --build -``` - -Маршрутизация: - -* `/api/v1/*`, `/agency/*`, `/whitelist/*` и устаревший `/feedback/*` идут в Rocket -* остальные запросы идут в SvelteKit SSR +* `reports` — общий протокол для отправки отчетов +* `website` — Rocket API и серверная логика +* `probe` — [Cheburcheck Probe](probe/README.md) +* `frontend` — SvelteKit SSR-интерфейс + +--- + +## Запуск через Docker Compose + +```sh +docker compose up --build +``` + +По умолчанию nginx слушает `http://localhost:8080`. Порт можно изменить через +`HTTP_PORT`, например: + +```sh +HTTP_PORT=80 docker compose up --build +``` + +Маршрутизация: + +* `/api/v1/*`, `/agency/*`, `/whitelist/*` идут в Rocket +* остальные запросы идут в SvelteKit SSR --- diff --git a/docker-compose.yaml b/docker-compose.yaml index 4a13c61..56b1165 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -4,6 +4,7 @@ services: depends_on: - frontend - website + - rmqtt ports: - "${HTTP_PORT:-8080}:80" volumes: @@ -28,5 +29,21 @@ services: ROCKET_ADDRESS: "::" ROCKET_PORT: 8000 DATABASE_URL: "${DATABASE_URL}" + MQTT_ADMIN_TOKEN: "${MQTT_ADMIN_TOKEN}" + MQTT_HOST: rmqtt + MQTT_PORT: 11883 expose: - "8000" + + rmqtt: + image: docker.io/rmqtt/rmqtt:latest + depends_on: + - website + volumes: + - ./rmqtt/rmqtt.toml:/app/rmqtt/rmqtt.toml:ro + - ./rmqtt/rmqtt-plugins:/app/rmqtt/rmqtt-plugins:ro + expose: + - "8080" + - "1883" + - "11883" + - "6060" diff --git a/frontend/src/lib/api/check.ts b/frontend/src/lib/api/check.ts index 42ba83e..78b175c 100644 --- a/frontend/src/lib/api/check.ts +++ b/frontend/src/lib/api/check.ts @@ -23,6 +23,7 @@ type ApiCheckResponse = { blocked: boolean; rkn_domain?: string | null; ips: string[]; + reverse_lookup: string[]; blocked_subnets: string[]; cdn_providers: Record; geo: { @@ -46,6 +47,7 @@ export type CheckResult = { whitelist?: { lastOk?: string | null } | null; domain?: string | null; ips: string[]; + reverseLookup: string[]; subnetSize?: string | null; geo: { organisation?: string | null; location: string; asn?: string | null }; providers: { name: string; networks: ApiNetworkRecord[] }[]; @@ -97,6 +99,7 @@ export async function fetchCheck(target: string): Promise { : null, domain: data.rkn_domain, ips: data.ips, + reverseLookup: data.reverse_lookup, subnetSize: data.subnet_size, geo: data.geo, providers: Object.entries(data.cdn_providers).map(([name, networks]) => ({ diff --git a/frontend/src/lib/api/probe.ts b/frontend/src/lib/api/probe.ts new file mode 100644 index 0000000..fea24db --- /dev/null +++ b/frontend/src/lib/api/probe.ts @@ -0,0 +1,73 @@ +export type ProbeHostResult = { + host_id: string; + host: string; + probe_evidence: + | { + type: "ConnectionError" | "ClientHello" | "Good"; + } + | { + type: "DataTimeout"; + bytes: number; + }; +}; + +export type ProbeResult = { + job_id: string; + probe_id: string; + region?: string | null; + provider?: string | null; + asn?: string | null; + verdict: "uncertain" | "sni_block" | "whitelist" | "ok"; + host_results: ProbeHostResult[]; +}; + +export type ProbeStatus = { + id: string; + target: string; + online_probes: number; + response_count: number; + status: "started" | "progress" | "done" | "error"; +}; + +export function startProbeSSE( + id: string, + onResult: (result: ProbeResult) => void, + onStatus: (status: Partial) => void, +) { + const eventSource = new EventSource(`/api/v1/probe/${id}`); + + eventSource.addEventListener("started", (event) => { + const data = JSON.parse(event.data); + onStatus({ + id: data.id, + target: data.target, + online_probes: data.online_probes, + status: "started", + response_count: 0, + }); + }); + + eventSource.addEventListener("result", (event) => { + const data = JSON.parse(event.data) as ProbeResult; + onResult(data); + onStatus({ status: "progress" }); + }); + + eventSource.addEventListener("done", (event) => { + const data = JSON.parse(event.data); + onStatus({ + status: "done", + response_count: data.response_count, + online_probes: data.online_probes, + }); + eventSource.close(); + }); + + eventSource.onerror = (error) => { + console.error("Probe SSE error:", error); + onStatus({ status: "error" }); + eventSource.close(); + }; + + return () => eventSource.close(); +} diff --git a/frontend/src/lib/components/DetailRow.svelte b/frontend/src/lib/components/DetailRow.svelte index e5d3f1c..4f600ce 100644 --- a/frontend/src/lib/components/DetailRow.svelte +++ b/frontend/src/lib/components/DetailRow.svelte @@ -1,28 +1,39 @@
- {#if href} - - {label} - - {:else} - - {label} - - {/if} +
+ {#if Icon} +
{@render children()}
diff --git a/frontend/src/lib/components/Feedback.svelte b/frontend/src/lib/components/Feedback.svelte index 7b05944..62a0f0e 100644 --- a/frontend/src/lib/components/Feedback.svelte +++ b/frontend/src/lib/components/Feedback.svelte @@ -5,49 +5,30 @@ import { submitFeedback } from "$lib/api/feedback"; let { id, - theme = "clean", }: { id: string; - theme?: "blocked" | "clean" | "whitelist"; } = $props(); const feedbackMutation = createMutation(() => ({ mutationFn: submitFeedback, })); -const worksClass = $derived( - theme === "blocked" - ? "border-transparent bg-green-500 text-neutral-950 hover:bg-emerald-400" - : "border border-green-500 bg-transparent text-green-500 hover:bg-green-500/10", -); -const notWorksClass = $derived( - theme === "clean" - ? "border-transparent bg-red-500 text-neutral-100 hover:bg-red-400" - : "border border-red-500 bg-transparent text-red-500 hover:bg-red-500/10", -); -const statusClass = $derived( - theme === "blocked" - ? "text-red-400" - : theme === "clean" - ? "text-green-500" - : "text-neutral-300", -); const submit = (works: boolean) => { feedbackMutation.mutate({ id, works }); }; -
+
{#if feedbackMutation.isSuccess} -
-