diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ee24bcf4..af3f8241 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,12 +6,21 @@ on: env: CARGO_TERM_COLOR: always - REGISTRY: frknorg - API_IMAGE_NAME: pony-api - DOCKER_TAG: latest jobs: - build-agent: + tests: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + - name: Install Rust + uses: dtolnay/rust-toolchain@nightly + - name: Install dependencies + run: sudo apt-get update && sudo apt-get install -y protobuf-compiler + - name: Test + run: cargo test --lib + + build-node: + needs: [tests] runs-on: ubuntu-22.04 strategy: matrix: @@ -21,149 +30,99 @@ jobs: - armv7-unknown-linux-gnueabihf steps: - - name: Checkout - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - - name: Install latest rust toolchain - uses: actions-rs/toolchain@v1 + - name: Install Rust + uses: dtolnay/rust-toolchain@nightly with: - toolchain: nightly - default: true - override: true - - - name: Update apt package index - run: sudo apt-get update + targets: ${{ matrix.target }} - - name: Install protobuf-compiler - run: sudo apt-get install -y protobuf-compiler - - - name: Install zig - run: pip3 install ziglang - - - name: Install cargo-zigbuild - run: cargo install --locked cargo-zigbuild + - uses: Swatinem/rust-cache@v2 + with: + key: ${{ matrix.target }} - - name: Add target - run: rustup target add ${{ matrix.target }} + - name: Install Build Tools + run: | + sudo apt-get update && sudo apt-get install -y protobuf-compiler + pip3 install ziglang + cargo install cargo-zigbuild - - name: Build for ${{ matrix.target }} - run: cargo zigbuild --release --target ${{ matrix.target }} --bin agent --no-default-features + - name: Build Node + run: cargo zigbuild --release --target ${{ matrix.target }} --bin node --features xray,wireguard - - name: Upload artifacts + - name: Upload Artifact uses: actions/upload-artifact@v4 with: - name: agent-${{ matrix.target }} - path: target/${{ matrix.target }}/release/agent + name: fcore-node-${{ matrix.target }} + path: target/${{ matrix.target }}/release/node build-api: - runs-on: ubuntu-latest - + needs: [tests] + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 - - - name: Install latest rust toolchain - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - default: true - override: true - - - name: Update apt package index - run: sudo apt-get update - - - name: Install protobuf-compiler - run: sudo apt-get install -y protobuf-compiler - - - name: Build - run: cargo build --release --no-default-features --bin api - - - name: Upload artifacts + - name: Install Rust + uses: dtolnay/rust-toolchain@nightly + - name: Test Lib + run: cargo test --lib + - name: Build API + run: cargo build --release --bin api --no-default-features + - name: Upload Artifact uses: actions/upload-artifact@v4 with: - name: api + name: fcore-api path: target/release/api build-auth: - runs-on: ubuntu-latest - + needs: [tests] + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v4 - - - name: Install latest rust toolchain - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - default: true - override: true - - - name: Update apt package index - run: sudo apt-get update - - - name: Install protobuf-compiler - run: sudo apt-get install -y protobuf-compiler - - - name: Build - run: cargo build --release --no-default-features --bin auth - - - name: Upload artifacts + - name: Install Rust + uses: dtolnay/rust-toolchain@nightly + - uses: Swatinem/rust-cache@v2 + - name: Build Auth + run: cargo build --release --bin auth --no-default-features --features email + - name: Upload Artifact uses: actions/upload-artifact@v4 with: - name: auth + name: fcore-auth path: target/release/auth release: - name: Release - needs: [build-api, build-agent, build-auth] + name: Create Release + needs: [build-api, build-auth, build-node] runs-on: ubuntu-latest - steps: - name: Checkout uses: actions/checkout@v4 - - - name: Download all agent artifacts + - name: Download all artifacts uses: actions/download-artifact@v4 with: path: artifacts - pattern: agent-* + pattern: fcore-* merge-multiple: false - - name: Download API artifact - uses: actions/download-artifact@v4 - with: - name: api - path: artifacts/api - - - name: Download Auth artifact - uses: actions/download-artifact@v4 - with: - name: auth - path: artifacts/auth - - name: Prepare release files run: | - mkdir -p release - cp artifacts/agent-x86_64-unknown-linux-gnu/agent release/agent-x86_64 - cp artifacts/agent-aarch64-unknown-linux-gnu/agent release/agent-aarch64 - cp artifacts/agent-armv7-unknown-linux-gnueabihf/agent release/agent-armv7 - cp artifacts/api/api release/api-x86_64 - cp artifacts/auth/auth release/auth-x86_64 - chmod +x release/* - - - name: Release + mkdir -p release_dist + cp artifacts/fcore-node-x86_64-unknown-linux-gnu/node release_dist/node-x86_64 + cp artifacts/fcore-node-aarch64-unknown-linux-gnu/node release_dist/node-aarch64 + cp artifacts/fcore-node-armv7-unknown-linux-gnueabihf/node release_dist/node-armv7 + cp artifacts/fcore-api/api release_dist/api-x86_64 + cp artifacts/fcore-auth/auth release_dist/auth-x86_64 + chmod +x release_dist/* + + - name: Upload to GitHub Release uses: softprops/action-gh-release@v1 - if: startsWith(github.ref, 'refs/tags/') with: files: | - release/api-x86_64 - release/auth-x86_64 - release/agent-x86_64 - release/agent-aarch64 - release/agent-armv7 + release_dist/* docs/* README.md - config-agent-example.toml + config-node-example.toml config-api-example.toml config-auth-example.toml env: diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 6da226c2..8625eeff 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -1,175 +1,96 @@ -name: Pony Build +name: fcore build on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] + push: + branches: ["main"] + pull_request: + branches: ["main"] env: - CARGO_TERM_COLOR: always + CARGO_TERM_COLOR: always jobs: - build-agent: - runs-on: ubuntu-22.04 - strategy: - matrix: - target: - - x86_64-unknown-linux-gnu - - aarch64-unknown-linux-gnu - - armv7-unknown-linux-gnueabihf - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Install latest rust toolchain - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - default: true - override: true - - - name: Update apt package index - run: sudo apt-get update - - - name: Install protobuf-compiler - run: sudo apt-get install -y protobuf-compiler - - - name: Install zig - run: pip3 install ziglang - - - name: Install cargo-zigbuild - run: cargo install --locked cargo-zigbuild - - - name: Add target - run: rustup target add ${{ matrix.target }} - - - name: Build for ${{ matrix.target }} - run: cargo zigbuild --release --bin agent --no-default-features --target ${{ matrix.target }} - - - name: Upload binary for ${{ matrix.target }} - uses: actions/upload-artifact@v4 - with: - name: pony-agent-${{ matrix.target }} - path: target/${{ matrix.target }}/release/agent - - test-api: - runs-on: ubuntu-22.04 - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Install latest rust toolchain - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - default: true - override: true - - - name: Update apt package index - run: sudo apt-get update - - - name: Install protobuf-compiler - run: sudo apt-get install -y protobuf-compiler - - - name: Test API - run: cargo test - - build-api: - runs-on: ubuntu-22.04 - needs: [ test-api ] - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Install latest rust toolchain - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - default: true - override: true - - - name: Update apt package index - run: sudo apt-get update - - - name: Install protobuf-compiler - run: sudo apt-get install -y protobuf-compiler - - - name: Build API - run: cargo build --release --bin api --no-default-features - - - name: Upload API binary - uses: actions/upload-artifact@v4 - with: - name: pony-api - path: target/release/api - - build-auth: - runs-on: ubuntu-22.04 - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Install latest rust toolchain - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - default: true - override: true - - - name: Update apt package index - run: sudo apt-get update - - - name: Install protobuf-compiler - run: sudo apt-get install -y protobuf-compiler - - - name: Build API - run: cargo build --release --bin auth --no-default-features - - - name: Upload API binary - uses: actions/upload-artifact@v4 - with: - name: pony-auth - path: target/release/auth - - collect-binaries: - runs-on: ubuntu-latest - needs: [ build-agent, build-api ] - - steps: - - name: Download agent x86_64 - uses: actions/download-artifact@v4 - with: - name: pony-agent-x86_64-unknown-linux-gnu - path: collected/x86_64 - - - name: Download agent arm64 - uses: actions/download-artifact@v4 - with: - name: pony-agent-aarch64-unknown-linux-gnu - path: collected/arm64 - - - name: Download agent armv7 - uses: actions/download-artifact@v4 - with: - name: pony-agent-armv7-unknown-linux-gnueabihf - path: collected/armv7 - - - name: Download api - uses: actions/download-artifact@v4 - with: - name: pony-api - path: collected/api - - - name: Download auth - uses: actions/download-artifact@v4 - with: - name: pony-auth - path: collected/auth - - - name: Upload combined binaries - uses: actions/upload-artifact@v4 - with: - name: pony-binaries - path: collected/ + tests: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + - name: Install Rust + uses: dtolnay/rust-toolchain@nightly + - name: Install dependencies + run: sudo apt-get update && sudo apt-get install -y protobuf-compiler + - name: Test Lib + run: cargo test --lib + + build-node: + needs: [tests] + runs-on: ubuntu-22.04 + strategy: + matrix: + target: [x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu] + steps: + - uses: actions/checkout@v4 + - name: Install Rust + uses: dtolnay/rust-toolchain@nightly + with: + targets: ${{ matrix.target }} + - uses: Swatinem/rust-cache@v2 + with: + key: ${{ matrix.target }} + - name: Install Build Tools + run: | + sudo apt-get update && sudo apt-get install -y protobuf-compiler + pip3 install ziglang + cargo install cargo-zigbuild + - name: Build Node + run: cargo zigbuild --release --bin node --target ${{ matrix.target }} --features xray,wireguard + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: fcore-node-${{ matrix.target }} + path: target/${{ matrix.target }}/release/node + + build-api: + needs: [tests] + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + - name: Install Rust + uses: dtolnay/rust-toolchain@nightly + - name: Build API + run: cargo build --release --bin api --no-default-features + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: fcore-api + path: target/release/api + + build-auth: + needs: [tests] + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + - name: Install Rust + uses: dtolnay/rust-toolchain@nightly + - name: Build Auth + run: cargo build --release --bin auth --no-default-features --features email + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: fcore-auth + path: target/release/auth + + collect-binaries: + runs-on: ubuntu-latest + needs: [build-node, build-api, build-auth] + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: collected + pattern: fcore-* + merge-multiple: false + - name: Upload combined bundle + uses: actions/upload-artifact@v4 + with: + name: fcore-full-bundle + path: collected/ diff --git a/.gitignore b/.gitignore index 34ce3137..d37f8ce2 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ target snapshots/*.bin dev/data/* experimental/ +fcore.sublime-workspace diff --git a/Cargo.lock b/Cargo.lock index 6db0e357..7181478e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -70,56 +70,6 @@ dependencies = [ "libc", ] -[[package]] -name = "anstream" -version = "0.6.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" - -[[package]] -name = "anstyle-parse" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys 0.59.0", -] - [[package]] name = "anyhow" version = "1.0.93" @@ -260,26 +210,6 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "bindgen" -version = "0.71.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" -dependencies = [ - "bitflags 2.6.0", - "cexpr", - "clang-sys", - "itertools", - "log", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn 2.0.101", -] - [[package]] name = "bitflags" version = "1.3.2" @@ -291,9 +221,6 @@ name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" -dependencies = [ - "serde", -] [[package]] name = "bitvec" @@ -316,15 +243,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "bstr" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786a307d683a5bf92e6fd5fd69a7eb613751668d1d8d67d802846dfe367c62c8" -dependencies = [ - "memchr", -] - [[package]] name = "bumpalo" version = "3.16.0" @@ -390,15 +308,6 @@ dependencies = [ "shlex", ] -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom 7.1.3", -] - [[package]] name = "cfg-expr" version = "0.15.8" @@ -447,126 +356,6 @@ dependencies = [ "stacker", ] -[[package]] -name = "cityhash-rs" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93a719913643003b84bd13022b4b7e703c09342cd03b679c4641c7d2e50dc34d" - -[[package]] -name = "clang-sys" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" -dependencies = [ - "glob", - "libc", - "libloading", -] - -[[package]] -name = "clap" -version = "4.5.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.5.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.5.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.101", -] - -[[package]] -name = "clap_lex" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" - -[[package]] -name = "clickhouse" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2135bb9638e8c8c1e3d794f242099e57987059ba52e7e3de597e1d99b2c4a5a3" -dependencies = [ - "bstr", - "bytes", - "cityhash-rs", - "clickhouse-derive", - "futures", - "futures-channel", - "http-body-util", - "hyper 1.4.1", - "hyper-util", - "lz4_flex", - "replace_with", - "sealed", - "serde", - "static_assertions", - "thiserror 1.0.64", - "tokio", - "url", -] - -[[package]] -name = "clickhouse-derive" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d70f3e2893f7d3e017eeacdc9a708fbc29a10488e3ebca21f9df6a5d2b616dbb" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn 2.0.101", -] - -[[package]] -name = "colorchoice" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" - -[[package]] -name = "config" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7328b20597b53c2454f0b1919720c25c7339051c02b72b7e05409e00b14132be" -dependencies = [ - "async-trait", - "convert_case", - "json5", - "lazy_static", - "nom 7.1.3", - "pathdiff", - "ron", - "rust-ini", - "serde", - "serde_json", - "toml", - "yaml-rust", -] - [[package]] name = "console-api" version = "0.8.1" @@ -606,35 +395,6 @@ dependencies = [ "tracing-subscriber", ] -[[package]] -name = "const-random" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" -dependencies = [ - "const-random-macro", -] - -[[package]] -name = "const-random-macro" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" -dependencies = [ - "getrandom 0.2.15", - "once_cell", - "tiny-keccak", -] - -[[package]] -name = "convert_case" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "core-foundation" version = "0.9.4" @@ -725,12 +485,6 @@ version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - [[package]] name = "crypto-common" version = "0.1.6" @@ -787,23 +541,6 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" -[[package]] -name = "default-net" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c5a6569a908354d49b10db3c516d69aca1eccd97562fd31c98b13f00b73ca66" -dependencies = [ - "dlopen2", - "libc", - "memalloc", - "netlink-packet-core", - "netlink-packet-route 0.17.1", - "netlink-sys", - "once_cell", - "system-configuration 0.5.1", - "windows 0.48.0", -] - [[package]] name = "defguard_wireguard_rs" version = "0.7.2" @@ -815,7 +552,7 @@ dependencies = [ "log", "netlink-packet-core", "netlink-packet-generic", - "netlink-packet-route 0.22.0", + "netlink-packet-route", "netlink-packet-utils", "netlink-packet-wireguard", "netlink-sys", @@ -825,15 +562,6 @@ dependencies = [ "x25519-dalek", ] -[[package]] -name = "deranged" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" -dependencies = [ - "powerfmt", -] - [[package]] name = "digest" version = "0.10.7" @@ -867,26 +595,6 @@ dependencies = [ "syn 2.0.101", ] -[[package]] -name = "dlopen2" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b4f5f101177ff01b8ec4ecc81eead416a8aa42819a2869311b3420fa114ffa" -dependencies = [ - "libc", - "once_cell", - "winapi", -] - -[[package]] -name = "dlv-list" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" -dependencies = [ - "const-random", -] - [[package]] name = "either" version = "1.13.0" @@ -946,6 +654,55 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +[[package]] +name = "fcore" +version = "0.5.0-dev" +dependencies = [ + "async-trait", + "base32", + "base64 0.22.1", + "chrono", + "console-subscriber", + "dashmap", + "data-encoding", + "defguard_wireguard_rs", + "futures", + "hex", + "hmac", + "lettre", + "openssl", + "parking_lot", + "postgres-types", + "prost", + "prost-derive", + "qrcode", + "rand 0.8.5", + "reqwest", + "rkyv", + "serde", + "serde_json", + "serde_urlencoded", + "serde_yaml", + "sha2", + "sysinfo", + "thiserror 2.0.12", + "tokio", + "tokio-postgres", + "tokio-util", + "toml", + "tonic", + "tonic-build", + "tracing", + "tracing-subscriber", + "url", + "urlencoding", + "uuid", + "walkdir", + "warp", + "x25519-dalek", + "zmq", +] + [[package]] name = "fiat-crypto" version = "0.2.9" @@ -1142,12 +899,6 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" -[[package]] -name = "glob" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" - [[package]] name = "h2" version = "0.3.26" @@ -1195,12 +946,6 @@ dependencies = [ "ahash 0.7.8", ] -[[package]] -name = "hashbrown" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" - [[package]] name = "hashbrown" version = "0.14.5" @@ -1649,12 +1394,6 @@ version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" -[[package]] -name = "is_terminal_polyfill" -version = "1.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" - [[package]] name = "itertools" version = "0.10.5" @@ -1689,17 +1428,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "json5" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" -dependencies = [ - "pest", - "pest_derive", - "serde", -] - [[package]] name = "jwalk" version = "0.8.1" @@ -1750,22 +1478,6 @@ version = "0.2.167" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" -[[package]] -name = "libloading" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" -dependencies = [ - "cfg-if", - "windows-targets 0.52.6", -] - -[[package]] -name = "linked-hash-map" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" - [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -1799,12 +1511,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" -[[package]] -name = "lz4_flex" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75761162ae2b0e580d7e7c390558127e5f01b4194debd6221fd8c207fc80e3f5" - [[package]] name = "matchers" version = "0.1.0" @@ -1830,12 +1536,6 @@ dependencies = [ "digest", ] -[[package]] -name = "memalloc" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df39d232f5c40b0891c10216992c2f250c054105cb1e56f0fc9032db6203ecc1" - [[package]] name = "memchr" version = "2.7.4" @@ -1958,48 +1658,19 @@ dependencies = [ "netlink-packet-utils", ] -[[package]] -name = "netlink-packet-route" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053998cea5a306971f88580d0829e90f270f940befd7cf928da179d4187a5a66" -dependencies = [ - "anyhow", - "bitflags 1.3.2", - "byteorder", - "libc", - "netlink-packet-core", - "netlink-packet-utils", -] - [[package]] name = "netlink-packet-route" version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0e7987b28514adf555dc1f9a5c30dfc3e50750bbaffb1aec41ca7b23dcd8e4" -dependencies = [ - "anyhow", - "bitflags 2.6.0", - "byteorder", - "libc", - "log", - "netlink-packet-core", - "netlink-packet-utils", -] - -[[package]] -name = "netlink-packet-sock-diag" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a495cb1de50560a7cd12fdcf023db70eec00e340df81be31cedbbfd4aadd6b76" +checksum = "fc0e7987b28514adf555dc1f9a5c30dfc3e50750bbaffb1aec41ca7b23dcd8e4" dependencies = [ "anyhow", - "bitflags 1.3.2", + "bitflags 2.6.0", "byteorder", "libc", + "log", "netlink-packet-core", "netlink-packet-utils", - "smallvec", ] [[package]] @@ -2039,24 +1710,6 @@ dependencies = [ "log", ] -[[package]] -name = "netstat2" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6422b6a8c7635e8a82323e4cdf07a90e91901e07f4c1f0f3a245d54b4637e55c" -dependencies = [ - "bindgen", - "bitflags 2.6.0", - "byteorder", - "netlink-packet-core", - "netlink-packet-sock-diag", - "netlink-packet-utils", - "netlink-sys", - "num-derive", - "num-traits", - "thiserror 2.0.12", -] - [[package]] name = "nix" version = "0.29.0" @@ -2108,23 +1761,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - -[[package]] -name = "num-derive" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -2158,12 +1794,6 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" -[[package]] -name = "once_cell_polyfill" -version = "1.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" - [[package]] name = "openssl" version = "0.10.66" @@ -2218,16 +1848,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "ordered-multimap" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ed8acf08e98e744e5384c8bc63ceb0364e68a6854187221c18df61c4797690e" -dependencies = [ - "dlv-list", - "hashbrown 0.13.2", -] - [[package]] name = "overload" version = "0.1.1" @@ -2263,63 +1883,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" -[[package]] -name = "pathdiff" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" - [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" -[[package]] -name = "pest" -version = "2.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdbef9d1d47087a895abd220ed25eb4ad973a5e26f6a4367b038c25e28dfc2d9" -dependencies = [ - "memchr", - "thiserror 1.0.64", - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d3a6e3394ec80feb3b6393c725571754c6188490265c61aaf260810d6b95aa0" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94429506bde1ca69d1b5601962c73f4172ab4726571a59ea95931218cb0e930e" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn 2.0.101", -] - -[[package]] -name = "pest_meta" -version = "2.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac8a071862e93690b6e34e9a5fb8e33ff3734473ac0245b27232222c4906a33f" -dependencies = [ - "once_cell", - "pest", - "sha2", -] - [[package]] name = "petgraph" version = "0.6.5" @@ -2386,63 +1955,6 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" -[[package]] -name = "pony" -version = "0.4.11-dev" -dependencies = [ - "anyhow", - "async-trait", - "base32", - "base64 0.22.1", - "chrono", - "clap", - "clickhouse", - "config", - "console-subscriber", - "dashmap", - "data-encoding", - "default-net", - "defguard_wireguard_rs", - "futures", - "hex", - "hmac", - "lettre", - "netstat2", - "openssl", - "parking_lot", - "percent-encoding", - "postgres-types", - "prost", - "prost-derive", - "qrcode", - "rand 0.8.5", - "reqwest", - "rkyv", - "serde", - "serde_json", - "serde_urlencoded", - "serde_yaml", - "sha2", - "sysinfo", - "thiserror 2.0.12", - "time", - "tokio", - "tokio-postgres", - "tokio-util", - "toml", - "tonic", - "tonic-build", - "tracing", - "tracing-subscriber", - "url", - "urlencoding", - "uuid", - "walkdir", - "warp", - "x25519-dalek", - "zmq", -] - [[package]] name = "postgres-derive" version = "0.4.6" @@ -2498,12 +2010,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - [[package]] name = "ppv-lite86" version = "0.2.20" @@ -2847,12 +2353,6 @@ dependencies = [ "bytecheck", ] -[[package]] -name = "replace_with" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a8614ee435691de62bcffcf4a66d91b3594bf1428a5722e79103249a095690" - [[package]] name = "reqwest" version = "0.12.15" @@ -2888,7 +2388,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "sync_wrapper", - "system-configuration 0.6.1", + "system-configuration", "tokio", "tokio-native-tls", "tokio-rustls", @@ -2945,28 +2445,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "ron" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" -dependencies = [ - "base64 0.21.7", - "bitflags 2.6.0", - "serde", - "serde_derive", -] - -[[package]] -name = "rust-ini" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e2a3bcec1f113553ef1c88aae6c020a369d03d55b58de9869a0908930385091" -dependencies = [ - "cfg-if", - "ordered-multimap", -] - [[package]] name = "rustc-demangle" version = "0.1.24" @@ -3092,17 +2570,6 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" -[[package]] -name = "sealed" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22f968c5ea23d555e670b449c1c5e7b2fc399fdaec1d304a17cd48e288abc107" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.101", -] - [[package]] name = "security-framework" version = "2.11.1" @@ -3152,17 +2619,6 @@ dependencies = [ "syn 2.0.101", ] -[[package]] -name = "serde_derive_internals" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.101", -] - [[package]] name = "serde_json" version = "1.0.128" @@ -3317,12 +2773,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "stringprep" version = "0.1.5" @@ -3334,12 +2784,6 @@ dependencies = [ "unicode-properties", ] -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - [[package]] name = "subtle" version = "2.6.1" @@ -3399,18 +2843,7 @@ dependencies = [ "memchr", "ntapi", "rayon", - "windows 0.57.0", -] - -[[package]] -name = "system-configuration" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "system-configuration-sys 0.5.0", + "windows", ] [[package]] @@ -3421,17 +2854,7 @@ checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ "bitflags 2.6.0", "core-foundation", - "system-configuration-sys 0.6.0", -] - -[[package]] -name = "system-configuration-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" -dependencies = [ - "core-foundation-sys", - "libc", + "system-configuration-sys", ] [[package]] @@ -3532,34 +2955,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "time" -version = "0.3.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" -dependencies = [ - "deranged", - "num-conv", - "powerfmt", - "serde", - "time-core", -] - -[[package]] -name = "time-core" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" - -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - [[package]] name = "tinystr" version = "0.8.2" @@ -3916,12 +3311,6 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" -[[package]] -name = "ucd-trie" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" - [[package]] name = "unicase" version = "2.8.1" @@ -3955,12 +3344,6 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" -[[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - [[package]] name = "unsafe-libyaml" version = "0.2.11" @@ -4002,12 +3385,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - [[package]] name = "uuid" version = "1.11.0" @@ -4262,15 +3639,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows" version = "0.57.0" @@ -4392,21 +3760,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -4439,12 +3792,6 @@ dependencies = [ "windows_x86_64_msvc 0.53.0", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -4457,12 +3804,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -4475,12 +3816,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -4505,12 +3840,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -4523,12 +3852,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -4541,12 +3864,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -4559,12 +3876,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -4619,15 +3930,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "yaml-rust" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" -dependencies = [ - "linked-hash-map", -] - [[package]] name = "yoke" version = "0.8.1" diff --git a/Cargo.toml b/Cargo.toml index 47f2dde9..7bbefc85 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "pony" -version = "0.4.11-dev" +name = "fcore" +version = "0.5.0-dev" edition = "2021" build = "build.rs" @@ -15,26 +15,19 @@ panic = "abort" strip = true [dependencies] -anyhow = "1" async-trait = "0.1" base64 = "0.22" base32 = "0.5.1" chrono = { version = "0.4", features = ["serde", "rkyv"] } -config = "0.14" console-subscriber = {version = "0.4", optional = true} -clickhouse = { version = "0.13.0" } -clap = { version = "4.4", features = ["derive"] } dashmap = "6.1.0" -defguard_wireguard_rs = {version = "0.7.2", features=["serde"]} -default-net = "0.22" +defguard_wireguard_rs = {version = "0.7.2", features=["serde"], optional = true} futures = "0.3" -hex = "0.4" +hex = { version = "0.4", optional = true} hmac = "0.12" -netstat2 = { version = "0.11.1" } openssl = { version = "0.10", features = ["vendored"] } -percent-encoding = "2" -prost = { version = "0.13" } -prost-derive = { version = "0.13" } +prost = { version = "0.13", optional = true } +prost-derive = { version = "0.13", optional = true } rand = "0.8" reqwest = { version = "0.12", features = ["json", "rustls-tls"] } rkyv = { version = "0.7", features = ["std", "alloc", "validation", "uuid", ] } @@ -43,47 +36,46 @@ serde_json = "1.0" serde_urlencoded = "0.7" serde_yaml = "0.9" sysinfo = { version = "0.33"} -time = "0.3" thiserror = "2.0" -tonic = { version = "0.12" } +tonic = { version = "0.12", optional = true } toml = "0.8" tokio = { version = "1", features = ["full"] } tokio-postgres = { version="0.7", features=["with-uuid-1", "with-chrono-0_4", "with-serde_json-1"]} tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] } -postgres-types = { version = "0.2", features = ["derive"] } +postgres-types = { version = "0.2", features = ["derive"]} url = "2" uuid = { version = "1", features = ["serde", "v4"] } urlencoding = "2.1.3" qrcode = "0.14" x25519-dalek = { version = "2", features = ["static_secrets"] } -warp = "0.3" +warp = {version = "0.3"} zmq = "0.10" parking_lot = "0.12.5" - sha2 = "0.10" data-encoding = "2.5" -lettre = { version = "0.11", features = ["tokio1", "builder", "smtp-transport", "tokio1-native-tls"] } - - +lettre = { version = "0.11", features = ["tokio1", "builder", "smtp-transport", "tokio1-native-tls"], optional = true} [build-dependencies] -tonic-build = "0.12" +tonic-build = {version = "0.12", optional = true} walkdir = "2.0" [dev-dependencies] tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["fmt", "env-filter"] } +console-subscriber = { version = "0.4" } tokio-util = "0.7" [features] - default = [] debug = ["console-subscriber"] +wireguard = ["defguard_wireguard_rs"] +xray = ["prost", "prost-derive", "tonic", "tonic-build"] +email = ["lettre", "hex"] [[bin]] -name = "agent" -path = "src/bin/agent/main.rs" +name = "node" +path = "src/bin/node/main.rs" [[bin]] name = "api" @@ -93,6 +85,3 @@ path = "src/bin/api/main.rs" name = "auth" path = "src/bin/auth/main.rs" -[[bin]] -name = "utils" -path = "src/bin/utils.rs" diff --git a/build.rs b/build.rs index 79d5f46a..954d0c46 100644 --- a/build.rs +++ b/build.rs @@ -1,26 +1,28 @@ -use walkdir::WalkDir; - fn main() { - let proto_dir = "src/proto/xray/protobuf"; + #[cfg(feature = "xray")] + { + use walkdir::WalkDir; + let proto_dir = "src/proto/xray/protobuf"; - let proto_files: Vec = WalkDir::new(proto_dir) - .into_iter() - .filter_map(|entry| entry.ok()) - .filter(|entry| { - entry - .path() - .extension() - .map(|ext| ext == "proto") - .unwrap_or(false) - }) - .map(|entry| entry.path().to_str().unwrap().to_string()) - .collect(); + let proto_files: Vec = WalkDir::new(proto_dir) + .into_iter() + .filter_map(|entry| entry.ok()) + .filter(|entry| { + entry + .path() + .extension() + .map(|ext| ext == "proto") + .unwrap_or(false) + }) + .map(|entry| entry.path().to_str().unwrap().to_string()) + .collect(); - tonic_build::configure() - .build_client(true) - .build_server(false) - .compile_protos(&proto_files, &[proto_dir]) - .unwrap_or_else(|e| panic!("Failed to compile protos {:?}", e)); + tonic_build::configure() + .build_client(true) + .build_server(false) + .compile_protos(&proto_files, &[proto_dir]) + .unwrap_or_else(|e| panic!("Failed to compile protos {:?}", e)); - println!("cargo:rerun-if-changed={}", proto_dir); + println!("cargo:rerun-if-changed={}", proto_dir); + } } diff --git a/config-agent-example.toml b/config-agent-example.toml index b18c7502..9e9f7b69 100644 --- a/config-agent-example.toml +++ b/config-agent-example.toml @@ -1,48 +1,40 @@ -[node] -env = "experimental" -uuid = "ab514c21-aaaa-bbbb-91f7-32f8cb1aaaaa" -hostname = "darkmachine.frkn.local" -address = "192.168.1.100" -label = "🏴‍☠️ Darkmachine" -max_bandwidth_bps = 1000000000 -country = "RU" -type = "common" +[service] +snapshot_interval = 60 +snapshot_path = "snapshots/snapshot-agent.bin" +log_velel = "debug" +updates_endpoint_zmq = "tcp://localhost:3001" - -[agent] -local = false -snapshot_interval = 30 -snapshot_path = "snapshots/agent_snapshot.bin" +[metrics] +interval = 100 +publisher = "tcp://localhost:3002" [xray] enabled = true -xray_config_path = "/etc/xray/config.json" +path = "dev/xray-config.json" -[wg] +[h2] enabled = true -port = 51820 -interface = "wg0" +path = "dev/h2.yaml" -[h2] -enabled = false -path = "etc/frkn/h2.yaml" +[wg] +enabled = false +path = "dev/utun7.conf" [mtproto] enabled = true -port = 8443 -secret = "dd00112233445566778899aabbccddeeff" +path = "dev/teleproxy.toml" -[metrics] -enabled = true -publisher = "tcp://api-server-ip:5555" -interval = 10 +[node] +env = "experimental" +uuid = "ab514c21-aaaa-bbbb-cccc-32f8cb1ada40" +hostname = "darkmachine2.frkn.local" +default_interface = "ens0" +address = "192.168.1.100" +label = "Darkmachine 🏴‍☠️ " +max_bandwidth_bps = 1000000000 +country = "RU" +type = "common" [api] -endpoint = "https://api.frkn.org" -token = "your-super-secret-api-token" - -[logging] -level = "warn" - -[zmq] -endpoint = "tcp://api-server-ip:3000" +endpoint = "http://127.0.0.1:3000" +token = "supetsecrettoken" diff --git a/config-api-example.toml b/config-api-example.toml index 1b51571b..3b715279 100644 --- a/config-api-example.toml +++ b/config-api-example.toml @@ -1,31 +1,29 @@ -[api] +[service] +log_level = "debug" listen = "127.0.0.1" -port = 3005 -token = "your-super-secret-api-token" -metrics_interval = 60 -db_sync_interval_sec = 300 -subscription_restore_interval = 60 -subscription_expire_interval = 60 -max_points = 100000000 # max metrics points stored in Memory -retention_seconds = 604800 # 7 days -wiregaurd_network = "10.1.0.0/16" # IP address pool for Wireguard peers +port = 3000 +token = "supetsecrettoken" + key_sign_token = [69,106,53,108,101,105,57] -bonus_days = 7 # Days added by referal -system_refer_codes = ["FRKN", "mobile-dev"] # Refer codes used for tg-bot and website +bonus_days = 7 +system_refer_codes = ["WEB", "TG", "MOBILE"] +wireguard_network = "10.1.0.0/16" +updates_endpoint_zmq = "tcp://*:3001" [metrics] -reciever = "tcp://0.0.0.0:3001" -topic = ["metrics"] +reciever = "tcp://0.0.0.0:3002" +max_points = 10000 +retention_seconds = 604800 # 7 days -[logging] -level = "debug" +[tasks] +db_sync_interval_sec = 1000 +subscription_restore_interval = 600 +subscription_expire_interval = 600 -[zmq] -endpoint = "tcp://*:3000" [pg] host = "localhost" port = 5432 -db = "frkn_db" +db = "api" username = "postgres" -password = "password" +password = "password" \ No newline at end of file diff --git a/config-auth-eample.toml b/config-auth-eample.toml index 80d75280..bd24eb96 100644 --- a/config-auth-eample.toml +++ b/config-auth-eample.toml @@ -1,38 +1,41 @@ -[auth] -snapshot_interval = 30 -snapshot_path = "snapshots/auth_snapshot.bin" -web_host = "https://auth.frkn.org" + +[service] +zmq_updates_endpoint = "tcp://localhost:3000" listen = "127.0.0.1" -port = 3005 -email_file = "users_trials.csv" +port = 3002 +log_level = "debug" +snapshot_path = "snapshots/snapshot-auth.bin" +snapshot_interval = 60 +web_host = "http://localhost:8080" + [node] -env = "auth" -uuid = "ab514c21-aaaa-bbbb-91f7-32f8cb1aaaaa" -hostname = "darkmachine.frkn.local" -address = "192.168.1.100" -label = "🏴‍☠️ Darkmachine" -max_bandwidth_bps = 1000000000 +env = "experimental" +hostname = "darkmachine.local" +address = "192.168.1.10" +uuid = "9557b391-cccc-dddd-ffff-6cbdd74fffff" +label = "🏴‍☠️ Auth DarkMachine" +max_bandwidth_bps = 1000000000 #1gbps country = "RU" type = "common" +[metrics] +interval = 30 +publisher = "tcp://127.0.0.1:3002" + [api] -endpoint = "http://localhost:3005" -token = "your-super-secret-api-token" +endpoint = "http://127.0.0.1:3001" +token = "supetsecrettoken" [smtp] -server = "smtp.gmail.com" +email_file = "users_trials.csv" +email_sign_token = [85,118,97,56,119] +server = "smtp.yandex.ru" +username = "hehe@hehe.org" +password = "PASSWORD" port = 587 -username = "notifications@frkn.org" -password = "app-password-here" -from = "FRKN Privacy " - -[metrics] -publisher = "tcp://127.0.0.1:5555" -interval = 60 - -[logging] -level = "info" -[zmq] -endpoint = "tcp://localhost:3000" +from = "Privacy Company " +title = "Тест-Драйв активирован 🏴‍☠️" +company_name = "Privacy Company" +support = "https://t.me/hehe_support" diff --git a/dev/api.requests b/dev/api.requests new file mode 100644 index 00000000..377b60f6 --- /dev/null +++ b/dev/api.requests @@ -0,0 +1,43 @@ + +requests.get('http://localhost:3000/healthcheck') +requests.get('http://localhost:3000/nodes') + + +requests.post( + 'http://localhost:5005/subscription', + headers={ + 'Authorization': 'Bearer supetsecrettoken', + 'Content-Type': 'application/json' + }, + json={ + "env": "experimental", + "days": 10 + } +) + + +requests.post( + 'http://localhost:3000/connection', + headers={ + 'Authorization': 'Bearer supetsecrettoken', + 'Content-Type': 'application/json' + }, + json={ + "env": "dev", + "proto": "Hysteria2", + "subscription_id": "ac01dba1-1190-4cc6-905b-f4a35f82cfee" + } +) + +requests.post( + 'http://localhost:3000/connection', + headers={ + 'Authorization': 'Bearer supetsecrettoken', + 'Content-Type': 'application/json' + }, + json={ + "env": "premium12345", + "proto": "VlessTcpReality", + "subscription_id": "ac01dba1-1190-4cc6-905b-f4a35f82cfee" + } +) \ No newline at end of file diff --git a/dev/commands.sql b/dev/commands.sql new file mode 100644 index 00000000..00fb77d8 --- /dev/null +++ b/dev/commands.sql @@ -0,0 +1,5 @@ + + + + +SELECT * FROM connections; \ No newline at end of file diff --git a/dev/utun7.conf b/dev/utun7.conf new file mode 100644 index 00000000..92e24fdb --- /dev/null +++ b/dev/utun7.conf @@ -0,0 +1,13 @@ +[Interface] +PrivateKey = gPUrecCLMjr1S7Q4/9LeroOQUlpmAcSiuv31qsbsUFc= +Address = 10.0.0.1/16 +DNS = 1.1.1.1 +ListenPort = 51821 + + + +[Peer] +PublicKey = 0wAoJhT0lxWVhdw7L2na3gM9NybBNgNSQPknNTpnqEk= +Endpoint = 78.17.28.66:51820 +AllowedIPs = 0.0.0.0/0 +PersistentKeepalive = 25 diff --git a/fcore.sublime-project b/fcore.sublime-project new file mode 100644 index 00000000..0322bff7 --- /dev/null +++ b/fcore.sublime-project @@ -0,0 +1,50 @@ +{ + "folders": [{ "path": "." }], + "debugger_configurations": [ + { + "name": "Debug: Node", + "type": "lldb", + "request": "launch", + "cargo": { + "args": ["build", "--bin", "node", "--features", "debug", ], + "env": { "RUSTFLAGS": "--cfg tokio_unstable" }, + }, + "executable": "${folder}/target/debug/node", + "program": "${folder}/target/debug/node", + "executable": "${folder}/target/debug/node", + "cwd": "${folder}", + "args": ["${folder}/experimental/config-node.toml"], + "env": { + "RUST_BACKTRACE": "full", + "RUST_LOG": "debug" + } + }, + { + "name": "Debug: API", + "type": "lldb", + "request": "launch", + "cargo": { + "args": ["build", "--bin", "agent", "--features", "debug"], + "env": { "RUSTFLAGS": "--cfg tokio_unstable" }, + }, + "executable": "${folder}/target/debug/api", + "program": "${folder}/target/debug/api", + "cwd": "${folder}", + "terminal": "integrated", + "args": ["${folder}/experimental/config-api.toml"], + "env": { + "RUST_BACKTRACE": "full", + "RUST_LOG": "debug" + } + }, + { + "name": "Debug: Auth", + "type": "lldb", + "request": "launch", + "cargo": { "args": ["build", "--bin", "auth"] }, + "executable": "${folder}/target/debug/auth", + "cwd": "${folder}", + "terminal": "integrated", + }, + ], +} diff --git a/src/bin/agent/snapshot.rs b/src/bin/agent/snapshot.rs deleted file mode 100644 index f8c3c1c8..00000000 --- a/src/bin/agent/snapshot.rs +++ /dev/null @@ -1,111 +0,0 @@ -use rkyv::Archive; -use std::sync::Arc; -use tokio::sync::Mutex; - -use pony::{ - ConnectionBaseOperations, Connections, Error, Result, SnapshotManager, Tag, WgApi, - XrayHandlerActions, XrayHandlerClient, -}; - -#[async_trait::async_trait] -pub trait SnapshotRestore { - async fn restore_connections( - &self, - xray_client: Option>>, - wg_client: Option, - ) -> Result<()>; -} - -#[async_trait::async_trait] -impl SnapshotRestore for SnapshotManager> -where - C: Archive + Send + Sync + Clone + 'static + ConnectionBaseOperations, -{ - async fn restore_connections( - &self, - xray_client: Option>>, - wg_client: Option, - ) -> Result<()> { - let mem = self.memory.read().await; - - if mem.is_empty() { - return Err(Error::Custom("Empty snapshot".into())); - } - - let conns: Vec<(uuid::Uuid, C)> = - mem.iter().map(|(id, conn)| (*id, conn.clone())).collect(); - - drop(mem); - - for (conn_id, conn) in conns { - let wg_client = wg_client.clone(); - let xray_client = xray_client.clone(); - - tokio::spawn(async move { - match conn.get_proto().proto() { - Tag::Wireguard => { - if let Some(wg) = conn.get_wireguard() { - if let Some(api) = wg_client.as_ref() { - if let Ok(pubkey) = &wg.keys.pubkey() { - if let Err(e) = api.create(pubkey, wg.address.clone()) { - tracing::error!( - "Failed to restore WireGuard connection {}: {}", - conn_id, - e - ); - } - } - } - } - } - Tag::VlessTcpReality - | Tag::VlessGrpcReality - | Tag::VlessXhttpReality - | Tag::Vmess => { - if let Some(client) = xray_client.as_ref() { - if let Err(e) = client - .create(&conn_id, conn.get_proto().proto(), None) - .await - { - tracing::error!( - "Failed to restore Xray connection {}: {}", - conn_id, - e - ); - } else { - tracing::debug!("Restored Xray connection {}", conn_id); - } - } - } - Tag::Shadowsocks => { - if let Some(password) = conn.get_password() { - if let Some(client) = xray_client.as_ref() { - if let Err(e) = client - .create(&conn_id, conn.get_proto().proto(), Some(password)) - .await - { - tracing::error!( - "Failed to restore Shadowsocks connection {}: {}", - conn_id, - e - ); - } else { - tracing::debug!("Restored Shadowsocks connection {}", conn_id); - } - } - } - } - Tag::Hysteria2 | Tag::Mtproto => { - tracing::warn!( - "Skipping unsupported connection {} with tag {:?}", - conn_id, - conn.get_proto().proto() - ); - } - } - }); - } - - Ok(()) - } -} diff --git a/src/bin/api/config.rs b/src/bin/api/config.rs index be1d068b..662f4e9b 100644 --- a/src/bin/api/config.rs +++ b/src/bin/api/config.rs @@ -1,42 +1,24 @@ use serde::Deserialize; use std::net::Ipv4Addr; -use pony::{Error, IpAddrMask, Result}; -use pony::{LoggingConfig, Settings}; +use fcore::{IpAddrMask, Result, Settings}; -#[derive(Clone, Debug, Deserialize, Default)] -pub struct PostgresConfig { - pub host: String, - pub port: u16, - pub db: String, - pub username: String, - pub password: String, -} - -#[derive(Clone, Default, Debug, Deserialize)] -pub struct MetricsRxConfig { - pub reciever: String, - pub topic: Vec, -} - -#[derive(Clone, Debug, Deserialize, Default)] -pub struct ZmqPublisherConfig { - pub endpoint: String, +#[derive(Clone, Debug, Deserialize)] +pub struct ServiceSettings { + pub service: ServiceConfig, + pub pg: PostgresConfig, + pub metrics: MetricsRxConfig, + pub tasks: TasksConfig, } -impl ZmqPublisherConfig { - pub fn validate(self) -> Result<()> { - if !self.endpoint.starts_with("tcp://") { - return Err(Error::Custom( - "ZMQ endpoint should start with tcp://".into(), - )); - } +impl Settings for ServiceSettings { + fn validate(&self) -> Result<()> { Ok(()) } } fn default_base_url() -> String { - "http://localhost:8000".to_string() + "http://localhost:8080".to_string() } fn default_wg_network() -> IpAddrMask { @@ -47,38 +29,47 @@ fn default_listen_address() -> Ipv4Addr { "127.0.0.1".parse().unwrap() } +fn default_log_level() -> String { + "debug".to_string() +} + #[derive(Clone, Debug, Deserialize)] -pub struct ApiServiceConfig { +pub struct ServiceConfig { #[serde(default = "default_listen_address")] pub listen: Ipv4Addr, pub port: u16, pub token: String, - pub db_sync_interval_sec: u64, - pub subscription_restore_interval: u64, - pub subscription_expire_interval: u64, pub key_sign_token: Vec, pub bonus_days: i64, pub system_refer_codes: Vec, - pub max_points: usize, - pub retention_seconds: i64, #[serde(default = "default_base_url")] pub base_url: String, #[serde(default = "default_wg_network")] pub wireguard_network: IpAddrMask, + #[serde(default = "default_log_level")] + pub log_level: String, + pub updates_endpoint_zmq: String, } -#[derive(Clone, Debug, Deserialize)] -pub struct ApiSettings { - pub api: ApiServiceConfig, - pub logging: LoggingConfig, - pub zmq: ZmqPublisherConfig, - pub pg: PostgresConfig, - pub metrics: MetricsRxConfig, +#[derive(Clone, Debug, Deserialize, Default)] +pub struct PostgresConfig { + pub host: String, + pub port: u16, + pub db: String, + pub username: String, + pub password: String, } -impl Settings for ApiSettings { - fn validate(&self) -> Result<()> { - self.zmq.clone().validate()?; - Ok(()) - } +#[derive(Clone, Debug, Deserialize, Default)] +pub struct TasksConfig { + pub db_sync_interval_sec: u64, + pub subscription_restore_interval: u64, + pub subscription_expire_interval: u64, +} + +#[derive(Clone, Default, Debug, Deserialize)] +pub struct MetricsRxConfig { + pub reciever: String, + pub max_points: usize, + pub retention_seconds: i64, } diff --git a/src/bin/api/http/filters.rs b/src/bin/api/http/filters.rs index dd7c0470..60c2cf80 100644 --- a/src/bin/api/http/filters.rs +++ b/src/bin/api/http/filters.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use warp::Filter; -use pony::{ +use fcore::{ Connection, ConnectionApiOperations, ConnectionBaseOperations, IpAddrMask, MetricStorage, NodeStorageOperations, SubscriptionOperations, }; diff --git a/src/bin/api/http/handlers/connection.rs b/src/bin/api/http/handlers/connection.rs index 25a03c44..1b8ac3c3 100644 --- a/src/bin/api/http/handlers/connection.rs +++ b/src/bin/api/http/handlers/connection.rs @@ -4,26 +4,27 @@ use std::net::{IpAddr, Ipv4Addr}; use tracing::{debug, error}; -use pony::{ - Connection, ConnectionApiOperations, ConnectionBaseOperations, ConnectionStorageApiOperations, - InboundConnLink, IpAddrMask, NodeStorageOperations, Proto, Status, Subscription, - SubscriptionOperations, SubscriptionStorageOperations, Tag, WgKeys, WgParam, +use fcore::{ + http::{ + helpers as http, MyRejection, + {request::ConnType, response::Instance}, + }, + utils, Connection, ConnectionApiOperations, ConnectionBaseOperations, + ConnectionStorageApiOperations, InboundConnLink, IpAddrMask, NodeStorageOperations, Proto, + Status, Subscription, SubscriptionOperations, SubscriptionStorageOperations, Tag, Topic, + WgKeys, WgParam, }; -use pony::http::helpers as http; -use pony::http::response::Instance; -use pony::http::MyRejection; -use pony::utils; - -use super::super::super::sync::{tasks::SyncOp, MemSync}; - -use super::super::param::{ConnQueryParam, ConnTypeParam}; -use super::super::request::{ConnCreateRequest, ConnectionInfoRequest}; +use super::super::{ + super::sync::{tasks::SyncOp, MemSync}, + param::ConnQueryParam, + request::{ConnCreateRequest, ConnectionInfoRequest}, +}; /// Handler get connection -// GET /connections +// POST /connections/sync pub async fn get_connections_handler( - req: ConnTypeParam, + req: ConnType, memory: MemSync, ) -> Result where @@ -40,8 +41,10 @@ where S: SubscriptionOperations + Send + Sync + Clone + 'static + PartialEq + From, { let mem = memory.memory.read().await; + tracing::debug!("POST /connections/sync {:?}", req.clone()); + let proto = req.proto; - let topic = req.topic; + let topic = req.topic.try_into()?; let last_update = req.last_update; let env = req.env; @@ -51,9 +54,7 @@ where .filter(|(_, conn)| { !conn.get_deleted() && conn.get_proto().proto() == proto - && env - .as_ref() - .is_none_or(|e| conn.get_env().to_string() == *e) + && (proto == Tag::Hysteria2 || conn.get_env() == env) && last_update.is_none_or(|ts| conn.get_modified_at().timestamp() as u64 >= ts) }) .collect(); @@ -224,11 +225,11 @@ where let topic = if let Some(_token) = conn.get_token() { // Hysteria2 uses external auth provided which handles all envs - Some("auth".to_string()) + Some(Topic::Auth) } else if conn.get_proto().is_mtproto() { None } else { - Some(conn.get_env().to_string()) + Some(conn.get_env().into()) }; if let Some(topic) = topic { diff --git a/src/bin/api/http/handlers/key.rs b/src/bin/api/http/handlers/key.rs index 63d9d862..b49d660c 100644 --- a/src/bin/api/http/handlers/key.rs +++ b/src/bin/api/http/handlers/key.rs @@ -1,18 +1,16 @@ use tracing::error; -use pony::http::helpers as http; -use pony::http::response::Instance; - -use pony::{ +use fcore::{ + http::{helpers as http, response::Instance}, Connection, ConnectionApiOperations, ConnectionBaseOperations, Distributor, Error, Key, - NodeStorageOperations, Status, SubscriptionOperations, + NodeStorageOperations, Status, Subscription, SubscriptionOperations, }; -use super::super::super::sync::{tasks::SyncOp, MemSync}; - -use super::super::param::KeyQueryParams; -use super::super::request::ActivateKeyReq; -use super::super::request::KeyReq; +use super::super::{ + super::sync::{tasks::SyncOp, MemSync}, + param::KeyQueryParams, + request::{ActivateKeyReq, KeyReq}, +}; /// Get specific & validate key handler pub async fn get_key_validate_handler( @@ -119,14 +117,8 @@ where + 'static + From + PartialEq, - S: SubscriptionOperations - + Send - + Sync - + Clone - + 'static - + std::convert::From - + std::cmp::PartialEq, - pony::Connection: From, + S: SubscriptionOperations + Send + Sync + Clone + 'static + From + PartialEq, + Connection: From, { let key_db = memory.db.key(); diff --git a/src/bin/api/http/handlers/metrics.rs b/src/bin/api/http/handlers/metrics.rs index 1bbb1926..5737d727 100644 --- a/src/bin/api/http/handlers/metrics.rs +++ b/src/bin/api/http/handlers/metrics.rs @@ -3,7 +3,7 @@ use futures::{SinkExt, StreamExt}; use std::collections::BTreeMap; use std::sync::Arc; -use pony::MetricStorage; +use fcore::MetricStorage; pub async fn handle_ws_client( socket: warp::ws::WebSocket, diff --git a/src/bin/api/http/handlers/mod.rs b/src/bin/api/http/handlers/mod.rs index 16dd6120..647bbc85 100644 --- a/src/bin/api/http/handlers/mod.rs +++ b/src/bin/api/http/handlers/mod.rs @@ -6,11 +6,9 @@ pub mod subscription; use warp::http::StatusCode; -use pony::http::ResponseMessage; - -use pony::{ - Connection, ConnectionApiOperations, ConnectionBaseOperations, NodeStorageOperations, - SubscriptionOperations, +use fcore::{ + http::ResponseMessage, Connection, ConnectionApiOperations, ConnectionBaseOperations, + NodeStorageOperations, SubscriptionOperations, }; use crate::sync::MemSync; diff --git a/src/bin/api/http/handlers/node.rs b/src/bin/api/http/handlers/node.rs index 96468d4b..02b57115 100644 --- a/src/bin/api/http/handlers/node.rs +++ b/src/bin/api/http/handlers/node.rs @@ -1,19 +1,17 @@ use std::sync::Arc; use warp::http::StatusCode; -use pony::http::IdResponse; -use pony::http::ResponseMessage; - -use pony::{ +use fcore::{ + http::{IdResponse, ResponseMessage}, Connection, ConnectionApiOperations, ConnectionBaseOperations, MetricStorage, NodeMetricInfo, NodeResponse, NodeStatus, NodeStorageOperations, Status, Subscription, SubscriptionOperations, }; -use super::super::super::sync::tasks::SyncOp; -use super::super::super::sync::MemSync; -use super::super::param::NodeIdParam; -use super::super::param::NodesQueryParams; -use super::super::request::NodeRequest; +use super::super::{ + super::sync::{tasks::SyncOp, MemSync}, + param::{NodeIdParam, NodesQueryParams}, + request::NodeRequest, +}; // Register node handler // POST /node diff --git a/src/bin/api/http/handlers/subscription.rs b/src/bin/api/http/handlers/subscription.rs index b979ac1e..2aca0bc5 100644 --- a/src/bin/api/http/handlers/subscription.rs +++ b/src/bin/api/http/handlers/subscription.rs @@ -1,30 +1,27 @@ use base64::Engine; -use chrono::DateTime; -use chrono::Utc; -use pony::InboundClashConfig; +use chrono::{DateTime, Utc}; use std::collections::HashSet; use std::net::Ipv4Addr; -use warp::http::Response; -use warp::http::StatusCode; - -use pony::http::helpers as http; -use pony::http::response::{EnvInfo, Instance, SubscriptionResponse}; -use pony::http::ResponseMessage; - -use pony::{ - get_uuid_last_octet_simple, Connection, ConnectionApiOperations, ConnectionBaseOperations, - ConnectionStorageApiOperations, Env, Inbound, InboundConnLink, MetricStorage, - NodeStorageOperations, Status, Subscription, SubscriptionOperations, - SubscriptionStorageOperations, Tag, +use warp::http::{Response, StatusCode}; + +use fcore::http::{ + helpers as http, + response::{EnvInfo, Instance, SubscriptionResponse}, + ResponseMessage, }; -use crate::http::request::FormatReq; +use fcore::{ + utils::get_uuid_last_octet_simple, Connection, ConnectionApiOperations, + ConnectionBaseOperations, ConnectionStorageApiOperations, Env, Inbound, InboundClashConfig, + InboundConnLink, MetricStorage, NodeStorageOperations, Status, Subscription, + SubscriptionOperations, SubscriptionStorageOperations, Tag, +}; -use super::super::super::sync::tasks::SyncOp; -use super::super::super::sync::MemSync; -use super::super::param::SubIdQueryParam; -use super::super::request::Subscription as SubReq; -use super::super::request::SubscriptionInfoRequest; +use super::super::super::sync::{tasks::SyncOp, MemSync}; +use super::super::{ + param::SubIdQueryParam, + request::{FormatReq, Subscription as SubReq, SubscriptionInfoRequest}, +}; /// Handler creates subscription // POST /subscription diff --git a/src/bin/api/http/mod.rs b/src/bin/api/http/mod.rs index 533680e0..d7bf09fb 100644 --- a/src/bin/api/http/mod.rs +++ b/src/bin/api/http/mod.rs @@ -1,8 +1,8 @@ -use pony::http::AuthError; -use pony::http::MethodError; use warp::reject; use warp::{http::StatusCode, Rejection, Reply}; +use fcore::http::{AuthError, MethodError}; + mod filters; pub(crate) mod handlers; pub(crate) mod param; diff --git a/src/bin/api/http/param.rs b/src/bin/api/http/param.rs index 063cb36f..4a413653 100644 --- a/src/bin/api/http/param.rs +++ b/src/bin/api/http/param.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use pony::{Code, Tag}; +use fcore::Code; #[derive(Clone, Debug, Deserialize, Serialize)] pub struct SubIdQueryParam { @@ -22,14 +22,6 @@ pub struct ConnQueryParam { pub id: uuid::Uuid, } -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct ConnTypeParam { - pub proto: Tag, - pub last_update: Option, - pub env: Option, - pub topic: String, -} - #[derive(Serialize, Deserialize)] pub struct KeyQueryParams { pub key: Code, diff --git a/src/bin/api/http/request.rs b/src/bin/api/http/request.rs index dd767837..2b9e4e16 100644 --- a/src/bin/api/http/request.rs +++ b/src/bin/api/http/request.rs @@ -5,7 +5,7 @@ use std::net::Ipv4Addr; use std::collections::HashSet; -use pony::{Env, Error, Inbound, Node, NodeStatus, NodeType, Tag}; +use fcore::{Env, Error, Inbound, Node, NodeStatus, NodeType, Tag}; #[derive(Clone, Debug, Deserialize, Serialize)] pub enum TagReq { diff --git a/src/bin/api/http/routes.rs b/src/bin/api/http/routes.rs index c79e23e6..c1f5405f 100644 --- a/src/bin/api/http/routes.rs +++ b/src/bin/api/http/routes.rs @@ -2,35 +2,28 @@ use async_trait::async_trait; use std::sync::Arc; use warp::Filter; -use pony::http::filters::{auth, with_i64}; - -use pony::{ +use fcore::{ + http::filters::{auth, with_i64}, Connection, ConnectionApiOperations, ConnectionBaseOperations, NodeStorageOperations, Result, Subscription, SubscriptionOperations, }; -use super::super::api::Api; -use super::filters::*; -use super::handlers::connection::*; -use super::handlers::key::*; -use super::handlers::metrics::*; -use super::handlers::node::*; -use super::handlers::subscription::*; -use super::param::*; -use super::rejection; -use super::request::*; - -use super::super::config::ApiServiceConfig; - -use super::handlers::healthcheck_handler; +use super::{ + super::{config::ServiceConfig, service::Service}, + filters::*, + handlers::{connection::*, healthcheck_handler, key::*, metrics::*, node::*, subscription::*}, + param::*, + rejection, + request::*, +}; #[async_trait] pub trait Http { - async fn run(&self, params: ApiServiceConfig) -> Result<()>; + async fn run(&self, params: ServiceConfig) -> Result<()>; } #[async_trait] -impl Http for Api +impl Http for Service where C: ConnectionBaseOperations + ConnectionApiOperations @@ -47,8 +40,8 @@ where S: SubscriptionOperations + Send + Sync + Clone + 'static + PartialEq + From, { - async fn run(&self, params: ApiServiceConfig) -> Result<()> { - let auth = auth(Arc::new(self.settings.api.token.clone())); + async fn run(&self, params: ServiceConfig) -> Result<()> { + let auth = auth(Arc::new(self.settings.service.token.clone())); let cors = warp::cors() .allow_origin(params.base_url.as_str()) @@ -122,30 +115,30 @@ where .and_then(put_subscription_handler); // Connections Routes + let get_a_connection_route = warp::path!("connection") + .and(warp::get()) + .and(auth.clone()) + .and(warp::query::()) + .and(with_sync(self.sync.clone())) + .and_then(get_connection_handler); + let get_wg_connections_info_route = warp::path!("info" / "connections" / "wireguard") .and(warp::get()) .and(warp::query::()) .and(with_sync(self.sync.clone())) .and_then(wireguard_connections_handler); + let get_mtproto_connections_info_route = warp::path!("info" / "connections" / "mtproto") .and(warp::get()) .and(warp::query::()) .and(with_sync(self.sync.clone())) .and_then(mtproto_connections_handler); - let get_connection_route = warp::get() - .and(warp::path("connection")) - .and(warp::path::end()) - .and(auth.clone()) - .and(warp::query::()) - .and(with_sync(self.sync.clone())) - .and_then(get_connection_handler); - - let get_connections_route = warp::get() - .and(warp::path("connections")) - .and(warp::path::end()) + let post_connections_sync_route = warp::path("connections") + .and(warp::path("sync")) + .and(warp::post()) .and(auth.clone()) - .and(warp::query::()) + .and(warp::body::json()) .and(with_sync(self.sync.clone())) .and_then(get_connections_handler); @@ -234,12 +227,12 @@ where .or(get_node_route) .or(post_node_register_route) // Connection - .or(get_connection_route) - .or(get_connections_route) .or(post_connection_route) + .or(post_connections_sync_route) .or(delete_connection_route) .or(get_mtproto_connections_info_route) .or(get_wg_connections_info_route) + .or(get_a_connection_route) // Key .or(get_key_validation_route) .or(post_key_route) @@ -251,7 +244,7 @@ where .with(cors); warp::serve(routes) - .run((self.settings.api.listen, self.settings.api.port)) + .run((self.settings.service.listen, self.settings.service.port)) .await; Ok(()) diff --git a/src/bin/api/main.rs b/src/bin/api/main.rs index d57b37da..63d6c585 100644 --- a/src/bin/api/main.rs +++ b/src/bin/api/main.rs @@ -2,35 +2,35 @@ use std::sync::Arc; use tokio::sync::RwLock; use tokio::time::Duration; -use pony::{ - measure_time, utils::level_from_settings, MetricStorage, Publisher, Result, Settings, - Subscriber, BANNER, VERSION, +use fcore::{ + utils::level_from_settings, utils::measure_time, MetricStorage, Publisher, Result, Settings, + Subscriber, Topic, BANNER, VERSION, }; use tracing::{debug, error, info}; -use crate::api::Api; -use crate::api::ApiState; -use crate::api::Cache; -use crate::config::ApiSettings; -use crate::http::routes::Http; -use crate::metrics::MetricWorker; -use crate::postgres::pg::PgContext; -use crate::sync::MemSync; -use crate::tasks::Tasks; - -mod api; +use crate::{ + config::ServiceSettings, + http::routes::Http, + metrics::MetricWorker, + postgres::pg::PgContext, + service::{Cache, Service, State}, + sync::MemSync, + tasks::Tasks, +}; + mod config; mod http; mod metrics; mod postgres; +mod service; mod sync; mod tasks; #[tokio::main] async fn main() -> Result<()> { + println!(">>> API Service {}", VERSION); println!("{}", BANNER); - println!(">>> {}", VERSION); #[cfg(feature = "debug")] console_subscriber::init(); @@ -40,13 +40,13 @@ async fn main() -> Result<()> { .expect("required config path as an argument"); println!("Config file {:?}", config_path); - let settings = ApiSettings::new(config_path); + let settings = ServiceSettings::from_file(config_path); settings.validate().expect("Wrong settings file"); println!(">>> Settings: {:?}", settings.clone()); tracing_subscriber::fmt() - .with_env_filter(level_from_settings(&settings.logging.level)) + .with_env_filter(level_from_settings(&settings.service.log_level)) .init(); let db = match PgContext::init(&settings.pg).await { @@ -57,34 +57,38 @@ async fn main() -> Result<()> { } }; - let publisher = Publisher::new(&settings.zmq.endpoint).await; - - let mem: Arc> = Arc::new(RwLock::new(Cache::new())); - let mem_sync = MemSync::new(mem.clone(), db.clone(), publisher.clone()); + let mem: Arc> = Arc::new(RwLock::new(Cache::new())); + let publisher: Publisher = Publisher::new(&settings.service.updates_endpoint_zmq).await?; + let mem_sync = MemSync::new(mem.clone(), db.clone(), publisher); let metric_storage = Arc::new(MetricStorage::new( - settings.api.max_points, - settings.api.retention_seconds, + settings.metrics.max_points, + settings.metrics.retention_seconds, + )); + let api_service = Arc::new(Service::new( + mem_sync.clone(), + settings.clone(), + metric_storage, )); - let api = Arc::new(Api::new(mem_sync.clone(), settings.clone(), metric_storage)); - - measure_time(api.get_state_from_db(), "Init PostgreSQL DB").await?; + measure_time(api_service.get_state_from_db(), "Init PostgreSQL DB").await?; - let api_clone = api.clone(); + let api_service_clone = api_service.clone(); tokio::spawn(async move { - api_clone - .periodic_db_sync(settings.api.db_sync_interval_sec) + api_service_clone + .periodic_db_sync(settings.tasks.db_sync_interval_sec) .await; }); debug!("Running metrics reciever task"); - let subscriber = Subscriber::new_bound(&settings.metrics.reciever, settings.metrics.topic); - MetricWorker::start(api.metrics.clone(), subscriber).await; + let subscriber: Subscriber = + Subscriber::new_bound(&settings.metrics.reciever, vec![Topic::Metrics])?; + + MetricWorker::start(api_service.metrics.clone(), subscriber).await; info!("Metrics system initialized via MetricWorker"); - let metrics_storage = api.metrics.clone(); + let metrics_storage = api_service.metrics.clone(); tokio::spawn(async move { let mut interval = tokio::time::interval(std::time::Duration::from_secs(60)); loop { @@ -99,48 +103,52 @@ async fn main() -> Result<()> { }); tokio::spawn({ - let api = api.clone(); + let api_service = api_service.clone(); let job_interval = Duration::from_secs(60); info!("cleanup_expired_connections task started"); async move { - api.cleanup_expired_connections(job_interval.as_secs()) + api_service + .cleanup_expired_connections(job_interval.as_secs()) .await; } }); tokio::spawn({ - let api = api.clone(); - let job_interval = Duration::from_secs(settings.api.subscription_expire_interval); + let api_service = api_service.clone(); + let job_interval = Duration::from_secs(settings.tasks.subscription_expire_interval); info!("cleanup_expired_subscriptions task started"); async move { - api.cleanup_expired_subscriptions(job_interval.as_secs()) + api_service + .cleanup_expired_subscriptions(job_interval.as_secs()) .await; } }); tokio::spawn({ - let api = api.clone(); - let job_interval = Duration::from_secs(settings.api.subscription_restore_interval); + let api_service = api_service.clone(); + let job_interval = Duration::from_secs(settings.tasks.subscription_restore_interval); info!("restore_subscriptions task started"); async move { - api.restore_subscriptions(job_interval.as_secs()).await; + api_service + .restore_subscriptions(job_interval.as_secs()) + .await; } }); - let api = api.clone(); - let api_settings = settings.api.clone(); - let api_handle = tokio::spawn(async move { - if let Err(e) = api.run(api_settings).await { + let api_service = api_service.clone(); + let service_settings = settings.service.clone(); + let service_handle = tokio::spawn(async move { + if let Err(e) = api_service.run(service_settings).await { error!("API server exited with error: {}", e); } }); let res: Result<()> = tokio::select! { - _ = api_handle => { + _ = service_handle => { println!("API server finished"); Ok(()) } diff --git a/src/bin/api/metrics.rs b/src/bin/api/metrics.rs index 9cd6b205..b0d2ae1e 100644 --- a/src/bin/api/metrics.rs +++ b/src/bin/api/metrics.rs @@ -1,7 +1,7 @@ use rkyv::Deserialize; use std::sync::Arc; -use pony::{MetricEnvelope, MetricStorage, Subscriber}; +use fcore::{MetricEnvelope, MetricStorage, Subscriber}; pub struct MetricWorker; diff --git a/src/bin/api/postgres/connection.rs b/src/bin/api/postgres/connection.rs index 81fa358b..8d57768a 100644 --- a/src/bin/api/postgres/connection.rs +++ b/src/bin/api/postgres/connection.rs @@ -5,7 +5,7 @@ use serde::Serialize; use std::sync::Arc; use tokio::sync::Mutex; -use pony::{ +use fcore::{ Connection, ConnectionBaseOperations, Error, IpAddrMask, Proto, Result, Tag, WgKeys, WgParam, }; diff --git a/src/bin/api/postgres/keys.rs b/src/bin/api/postgres/keys.rs index aca9c8a6..c0860dca 100644 --- a/src/bin/api/postgres/keys.rs +++ b/src/bin/api/postgres/keys.rs @@ -5,7 +5,7 @@ use tokio::sync::Mutex; use super::pg::PgClientManager; -use pony::{Key, Result}; +use fcore::{Key, Result}; pub struct PgKey { pub manager: Arc>, diff --git a/src/bin/api/postgres/node.rs b/src/bin/api/postgres/node.rs index 5b786ad7..a2bb3d09 100644 --- a/src/bin/api/postgres/node.rs +++ b/src/bin/api/postgres/node.rs @@ -8,7 +8,7 @@ use tokio::sync::Mutex; use tracing::{debug, error, warn}; -use pony::{ +use fcore::{ H2Settings, Inbound, IpAddrMask, Node, NodeStatus, NodeType, Result, WgKeys, WireguardSettings, }; diff --git a/src/bin/api/postgres/pg.rs b/src/bin/api/postgres/pg.rs index 233f4363..e81836f0 100644 --- a/src/bin/api/postgres/pg.rs +++ b/src/bin/api/postgres/pg.rs @@ -6,18 +6,18 @@ use tokio_postgres::NoTls; use tracing::{debug, error, trace, warn}; -use pony::{ +use fcore::{ Connection, ConnectionStorageApiOperations, Env, Node, NodeStorageOperations, Result, Status, Subscription, SubscriptionStorageOperations, }; -use super::super::api::Cache; -use super::super::config::PostgresConfig; -use super::connection::ConnRow; -use super::connection::PgConn; -use super::keys::PgKey; -use super::node::PgNode; -use super::subscription::PgSubscription; +use super::{ + super::{config::PostgresConfig, service::Cache}, + connection::{ConnRow, PgConn}, + keys::PgKey, + node::PgNode, + subscription::PgSubscription, +}; pub struct PgClientManager { config: PostgresConfig, diff --git a/src/bin/api/postgres/subscription.rs b/src/bin/api/postgres/subscription.rs index ec213eda..6930421e 100644 --- a/src/bin/api/postgres/subscription.rs +++ b/src/bin/api/postgres/subscription.rs @@ -2,7 +2,7 @@ use chrono::Utc; use std::sync::Arc; use tokio::sync::Mutex; -use pony::{Result, Subscription}; +use fcore::{Result, Subscription}; use super::pg::PgClientManager; diff --git a/src/bin/api/api.rs b/src/bin/api/service.rs similarity index 85% rename from src/bin/api/api.rs rename to src/bin/api/service.rs index 7bec06ae..a10c52bb 100644 --- a/src/bin/api/api.rs +++ b/src/bin/api/service.rs @@ -2,17 +2,16 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::sync::Arc; -use pony::{ +use fcore::{ Connection, ConnectionApiOperations, ConnectionBaseOperations, Connections, Env, MetricStorage, Node, NodeStorageOperations, Subscription, SubscriptionOperations, Subscriptions, }; -use super::config::ApiSettings; -use super::sync::MemSync; +use super::{config::ServiceSettings, sync::MemSync}; -pub type ApiState = Cache>, Connection, Subscription>; +pub type State = Cache>, Connection, Subscription>; -pub struct Api +pub struct Service where N: NodeStorageOperations + Send + Sync + Clone + 'static, C: ConnectionBaseOperations @@ -25,11 +24,11 @@ where S: SubscriptionOperations + Send + Sync + Clone + 'static, { pub sync: MemSync, - pub settings: ApiSettings, + pub settings: ServiceSettings, pub metrics: Arc, } -impl Api +impl Service where N: NodeStorageOperations + Send + Sync + Clone + 'static, C: ConnectionBaseOperations @@ -41,7 +40,11 @@ where + PartialEq, S: SubscriptionOperations + Send + Sync + Clone + 'static, { - pub fn new(sync: MemSync, settings: ApiSettings, metrics: Arc) -> Self { + pub fn new( + sync: MemSync, + settings: ServiceSettings, + metrics: Arc, + ) -> Self { Self { sync, settings, diff --git a/src/bin/api/sync/mod.rs b/src/bin/api/sync/mod.rs index 61273ea6..5b1c83c3 100644 --- a/src/bin/api/sync/mod.rs +++ b/src/bin/api/sync/mod.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use tokio::sync::RwLock; use super::{postgres::pg::PgContext, Cache}; -use pony::{ +use fcore::{ Connection, ConnectionApiOperations, ConnectionBaseOperations, NodeStorageOperations, Publisher, SubscriptionOperations, }; diff --git a/src/bin/api/sync/tasks.rs b/src/bin/api/sync/tasks.rs index 13c94d77..1a0c09a1 100644 --- a/src/bin/api/sync/tasks.rs +++ b/src/bin/api/sync/tasks.rs @@ -2,10 +2,10 @@ use chrono::{Duration, Utc}; use futures::future::join_all; use tracing::{debug, error, info, warn}; -use pony::{ +use fcore::{ Connection, ConnectionApiOperations, ConnectionBaseOperations, ConnectionStorageApiOperations, Env, Node, NodeStatus, NodeStorageOperations, Status, Subscription, SubscriptionOperations, - SubscriptionStorageOperations, SyncError, + SubscriptionStorageOperations, SyncError, Topic, }; use super::super::{http::request::Subscription as SubReq, postgres::connection::ConnRow}; @@ -260,16 +260,16 @@ where debug!("Connection {} successfully removed from database", conn_id); let msg = vec![conn.as_delete_message(conn_id)]; - let key = if conn.get_token().is_some() { - "auth".to_string() + let topic = if conn.get_token().is_some() { + Topic::Auth } else { - conn.get_env().to_string() + conn.get_env().into() }; match rkyv::to_bytes::<_, 1024>(&msg) { Ok(bytes) => { - info!("Publishing delete command to agent/node: {}", key); - if let Err(e) = self.publisher.send_binary(&key, bytes.as_ref()).await { + info!("Publishing delete command to topic: {}", topic); + if let Err(e) = self.publisher.send_binary(&topic, bytes.as_ref()).await { error!( "NETWORK ERROR: Failed to send delete signal for {} to bus: {:?}", conn_id, e @@ -348,16 +348,16 @@ where } }; - let key = if conn.get_token().is_some() { - "auth".to_string() + let topic = if conn.get_token().is_some() { + Topic::Auth } else { - conn.get_env().to_string() + conn.get_env().into() }; - if let Err(e) = this.publisher.send_binary(&key, bytes.as_ref()).await { + if let Err(e) = this.publisher.send_binary(&topic, bytes.as_ref()).await { error!( "Failed to send restore message for {} to {}: {:?}", - conn_id, key, e + conn_id, topic, e ); return None; } diff --git a/src/bin/api/tasks.rs b/src/bin/api/tasks.rs index 57cf2959..1a0b9da0 100644 --- a/src/bin/api/tasks.rs +++ b/src/bin/api/tasks.rs @@ -5,15 +5,16 @@ use std::time::Duration; use tracing::{debug, error, info, warn}; -use pony::{ +use fcore::{ measure_time, Connection, ConnectionBaseOperations, ConnectionStorageApiOperations, Env, Node, Result, Status, Subscription, SubscriptionOperations, }; -use super::api::Api; -use super::api::Cache; -use super::postgres::pg::Tasks as MemoryCacheTasks; -use super::sync::tasks::SyncOp; +use super::{ + postgres::pg::Tasks as MemoryCacheTasks, + service::{Cache, Service}, + sync::tasks::SyncOp, +}; #[async_trait::async_trait] pub trait Tasks { @@ -25,7 +26,7 @@ pub trait Tasks { } #[async_trait::async_trait] -impl Tasks for Api>, Connection, Subscription> { +impl Tasks for Service>, Connection, Subscription> { async fn cleanup_expired_connections(&self, interval_sec: u64) { let mut interval = tokio::time::interval(Duration::from_secs(interval_sec)); diff --git a/src/bin/auth/config.rs b/src/bin/auth/config.rs index d6f4e2e1..acf992cb 100644 --- a/src/bin/auth/config.rs +++ b/src/bin/auth/config.rs @@ -1,54 +1,68 @@ use serde::Deserialize; use std::net::Ipv4Addr; -use pony::Result; -use pony::{ - ApiAccessConfig, LoggingConfig, MetricsTxConfig, NodeConfigRaw, Settings, ZmqSubscriberConfig, -}; +use fcore::{ApiAccessConfig, MetricsTxConfig, NodeConfigRaw, Result, Settings}; -#[derive(Clone, Debug, Deserialize, Default)] -pub struct SmtpConfig { - pub server: String, - pub username: String, - pub password: String, - pub port: u16, - pub from: String, +#[derive(Clone, Debug, Deserialize)] +pub struct ServiceSettings { + pub service: ServiceConfig, + pub node: NodeConfigRaw, + pub api: ApiAccessConfig, + #[cfg(feature = "email")] + pub smtp: SmtpConfig, + pub metrics: MetricsTxConfig, +} - pub title: String, - pub company_name: String, - pub support: String, +impl Settings for ServiceSettings { + fn validate(&self) -> Result<()> { + Ok(()) + } } fn default_listen_address() -> Ipv4Addr { "127.0.0.1".parse().unwrap() } +fn default_listen_port() -> u16 { + 3000 +} + +fn default_cors_origin() -> String { + "http://localhost:8080".to_string() +} + +#[cfg(feature = "email")] +fn default_company_website() -> String { + "http://localhost:8080".to_string() +} + #[derive(Clone, Debug, Deserialize)] -pub struct AuthServiceConfig { - pub snapshot_interval: u64, - pub snapshot_path: String, - pub web_host: String, +pub struct ServiceConfig { + pub log_level: String, #[serde(default = "default_listen_address")] pub listen: Ipv4Addr, + #[serde(default = "default_listen_port")] pub port: u16, - pub email_file: String, - pub email_sign_token: Vec, -} - -#[derive(Clone, Debug, Deserialize)] -pub struct AuthServiceSettings { - pub logging: LoggingConfig, - pub auth: AuthServiceConfig, - pub zmq: ZmqSubscriberConfig, - pub node: NodeConfigRaw, - pub api: ApiAccessConfig, - pub smtp: SmtpConfig, - pub metrics: MetricsTxConfig, + pub snapshot_interval: u64, + pub snapshot_path: String, + #[serde(default = "default_cors_origin")] + pub origin: String, + pub updates_endpoint_zmq: String, } -impl Settings for AuthServiceSettings { - fn validate(&self) -> Result<()> { - self.zmq.clone().validate()?; - Ok(()) - } +#[cfg(feature = "email")] +#[derive(Clone, Debug, Deserialize, Default)] +pub struct SmtpConfig { + pub server: String, + pub username: String, + pub password: String, + pub port: u16, + pub from: String, + pub title: String, + pub company_name: String, + pub support: String, + pub email_file: String, + pub email_sign_token: Vec, + #[serde(default = "default_company_website")] + pub company_website: String, } diff --git a/src/bin/auth/email.rs b/src/bin/auth/email.rs index 7927c53a..ef5d1c11 100644 --- a/src/bin/auth/email.rs +++ b/src/bin/auth/email.rs @@ -3,52 +3,47 @@ use hmac::{Hmac, Mac}; use sha2::Sha256; use std::collections::HashMap; use std::sync::Arc; -use tokio::fs::File; -use tokio::fs::OpenOptions; -use tokio::io::AsyncWriteExt; -use tokio::io::{AsyncBufReadExt, BufReader}; -use tokio::sync::RwLock; +use tokio::{ + fs::{File, OpenOptions}, + io::AsyncWriteExt, + io::{AsyncBufReadExt, BufReader}, + sync::RwLock, +}; use super::config::SmtpConfig; type HmacSha256 = Hmac; -use lettre::transport::smtp::{ - authentication::Credentials, - client::{Tls, TlsParameters}, - AsyncSmtpTransport, +use lettre::{ + transport::smtp::{ + authentication::Credentials, + client::{Tls, TlsParameters}, + AsyncSmtpTransport, + }, + AsyncTransport, Message, Tokio1Executor, }; -use lettre::AsyncTransport; -use lettre::Message; -use lettre::Tokio1Executor; #[derive(Clone)] pub struct EmailStore { pub store: Arc>>>, - file: String, smtp: SmtpConfig, - secret: Vec, - pub web_host: String, - mailer: Arc>, } impl EmailStore { - pub fn new(file: String, smtp: SmtpConfig, secret: Vec, web_host: String) -> Self { + pub fn new(smtp: SmtpConfig) -> Self { let mailer = EmailStore::build_mailer(&smtp); Self { store: Arc::new(RwLock::new(HashMap::new())), - file, smtp, - secret, - web_host, mailer: Arc::new(mailer), } } fn hmac_email(&self, email: &str) -> String { - let mut mac = HmacSha256::new_from_slice(&self.secret).unwrap(); + let secret = &self.smtp.email_sign_token; + let mut mac = HmacSha256::new_from_slice(&secret).unwrap(); mac.update(email.as_bytes()); hex::encode(mac.finalize().into_bytes()) } @@ -76,7 +71,7 @@ impl EmailStore { let file = OpenOptions::new() .create(true) .append(true) - .open(&self.file) + .open(&self.smtp.email_file) .await; let line = format!( @@ -93,7 +88,7 @@ impl EmailStore { } pub async fn load_trials(&self) -> std::io::Result<()> { - let file = match File::open(&self.file).await { + let file = match File::open(&self.smtp.email_file).await { Ok(f) => f, Err(_) => return Ok(()), }; @@ -134,7 +129,7 @@ impl EmailStore { pub async fn send_email_background(&self, to: String, sub_id: uuid::Uuid) { let mailer = self.mailer.clone(); - let web_host = self.web_host.clone(); + let web_host = self.smtp.company_website.clone(); let from = self.smtp.from.clone(); let title = self.smtp.title.clone(); let company_name = self.smtp.company_name.clone(); diff --git a/src/bin/auth/filters.rs b/src/bin/auth/filters.rs index 1e20a018..e44cbb9f 100644 --- a/src/bin/auth/filters.rs +++ b/src/bin/auth/filters.rs @@ -1,8 +1,10 @@ -use pony::ApiAccessConfig; +use fcore::ApiAccessConfig; use warp::Filter; +#[cfg(feature = "email")] use super::email::EmailStore; +#[cfg(feature = "email")] pub fn with_store( store: EmailStore, ) -> impl Filter + Clone { diff --git a/src/bin/auth/handlers.rs b/src/bin/auth/handlers.rs index 8ffe8db7..845a1a1d 100644 --- a/src/bin/auth/handlers.rs +++ b/src/bin/auth/handlers.rs @@ -1,28 +1,26 @@ use std::sync::Arc; use tokio::sync::RwLock; -use pony::http::helpers as http; -use pony::http::response::Instance; - -use pony::{ +use fcore::{ + http::{helpers as http, response::Instance}, ApiAccessConfig, ConnectionBaseOperations, ConnectionStorageBaseOperations, Connections, Env, }; -use super::auth::DEFAULT_DAYS; -use super::auth::PROTOS; +#[cfg(feature = "email")] use super::email::EmailStore; use super::helpers::{activate_key, validate_key}; use super::helpers::{create_connection, create_subscription, get_subscription}; use super::http::HttpClient; use super::request; use super::response; +use super::service::DEFAULT_DAYS; +use super::service::PROTOS; pub async fn activate_key_handler( req: request::ActivateKey, http: HttpClient, api: ApiAccessConfig, ) -> Result { - // 1. Validate Key let key = match validate_key(&http, &api.endpoint, &api.token, &req.code).await { Ok(k) => k, Err(e) => { @@ -85,6 +83,7 @@ pub async fn activate_key_handler( )) } +#[cfg(feature = "email")] pub async fn trial_handler( req: request::Trial, store: EmailStore, diff --git a/src/bin/auth/helpers.rs b/src/bin/auth/helpers.rs index 2527ab2d..02053dae 100644 --- a/src/bin/auth/helpers.rs +++ b/src/bin/auth/helpers.rs @@ -1,12 +1,9 @@ -use pony::http::response::ResponseMessage; -use pony::http::response::SubscriptionResponse; -use pony::http::response::{Instance, InstanceWithId}; - -use pony::Subscription; - use serde::Deserialize; -use pony::{Code, Env, Key}; +use fcore::{ + http::response::{Instance, InstanceWithId, ResponseMessage, SubscriptionResponse}, + Code, Env, Error, Key, Result, Subscription, +}; use super::http::HttpClient; @@ -21,7 +18,7 @@ pub async fn validate_key( api_address: &str, api_token: &str, key: &Code, -) -> anyhow::Result { +) -> Result { let url = format!("{}/key/validate?key={}", api_address, key); tracing::debug!("URL = {}", url); @@ -36,7 +33,7 @@ pub async fn validate_key( match parsed.response.instance { Instance::Key(key) => Ok(key), - _ => anyhow::bail!("Unexpected instance type"), + _ => Err(Error::Custom("Unexpected instance type".into())), } } else { #[derive(Deserialize)] @@ -44,7 +41,9 @@ pub async fn validate_key( message: Option, } let err: ErrResp = serde_json::from_str(&text).unwrap_or(ErrResp { message: None }); - anyhow::bail!(err.message.unwrap_or_else(|| "Unknown error".to_string())) + Err(Error::Custom( + err.message.unwrap_or_else(|| "Unknown error".to_string()), + )) } } @@ -53,7 +52,7 @@ pub async fn get_subscription( api_address: &str, api_token: &str, subscription_id: &uuid::Uuid, -) -> anyhow::Result { +) -> Result { let url = format!("{}/subscription/{}", api_address, subscription_id); tracing::debug!("URL = {}", url); @@ -77,7 +76,9 @@ pub async fn get_subscription( message: Option, } let err: ErrResp = serde_json::from_str(&text).unwrap_or(ErrResp { message: None }); - anyhow::bail!(err.message.unwrap_or_else(|| "Unknown error".to_string())) + Err(Error::Custom( + err.message.unwrap_or_else(|| "Unknown error".to_string()), + )) } } @@ -87,7 +88,7 @@ pub async fn activate_key( api_token: &str, key: &Code, sub_id: &uuid::Uuid, -) -> anyhow::Result { +) -> Result { let url = format!("{}/key/activate?", api_address); tracing::debug!("URL = {}", url); @@ -110,7 +111,7 @@ pub async fn activate_key( match parsed.response.instance { Instance::Key(key) => Ok(key), - _ => anyhow::bail!("Unexpected instance type"), + _ => Err(Error::Custom("Unexpected instance type".into())), } } else { #[derive(Deserialize)] @@ -118,7 +119,9 @@ pub async fn activate_key( message: Option, } let err: ErrResp = serde_json::from_str(&text).unwrap_or(ErrResp { message: None }); - anyhow::bail!(err.message.unwrap_or_else(|| "Unknown error".to_string())) + Err(Error::Custom( + err.message.unwrap_or_else(|| "Unknown error".to_string()), + )) } } @@ -128,7 +131,7 @@ pub async fn create_subscription( api_token: &str, days: i64, referred_by: &str, -) -> anyhow::Result { +) -> Result { let url = format!("{}/subscription", api_address); tracing::debug!("URL = {}", url); @@ -150,7 +153,7 @@ pub async fn create_subscription( match parsed.response.instance { Instance::Subscription(sub) => Ok(sub), - _ => anyhow::bail!("Unexpected instance type"), + _ => Err(Error::Custom("Unexpected instance type".into())), } } else { #[derive(Deserialize)] @@ -158,7 +161,9 @@ pub async fn create_subscription( message: Option, } let err: ErrResp = serde_json::from_str(&text).unwrap_or(ErrResp { message: None }); - anyhow::bail!(err.message.unwrap_or_else(|| "Unknown error".to_string())) + Err(Error::Custom( + err.message.unwrap_or_else(|| "Unknown error".to_string()), + )) } } @@ -169,7 +174,7 @@ pub async fn create_connection( sub_id: &uuid::Uuid, api_address: &str, api_token: &str, -) -> anyhow::Result { +) -> Result { tracing::debug!("POST /connection {}", env); let res = auth_headers( @@ -190,7 +195,7 @@ pub async fn create_connection( tracing::debug!("Connection resp: {}", text); if text.is_empty() { - anyhow::bail!("empty connection response, status = {}", status); + Error::Custom(format!("empty connection response, status = {}", status)); } let parsed: ResponseMessage> = serde_json::from_str(&text)?; diff --git a/src/bin/auth/http.rs b/src/bin/auth/http.rs index cd398f43..488476f6 100644 --- a/src/bin/auth/http.rs +++ b/src/bin/auth/http.rs @@ -1,23 +1,17 @@ use async_trait::async_trait; -use reqwest::Client; -use reqwest::StatusCode; -use reqwest::Url; -use serde::{Deserialize, Serialize}; +use reqwest::{Client, StatusCode, Url}; -use pony::http::response::{Instance, InstanceWithId, ResponseMessage}; -use pony::{ConnectionBaseOperations, Error, Result, Tag}; +use fcore::{ + http::{ + request::ConnType, + response::{Instance, InstanceWithId, ResponseMessage}, + }, + ConnectionBaseOperations, Error, Result, Tag, Topic, +}; -use super::auth::AuthService; +use super::service::Service; pub type HttpClient = Client; -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct ConnTypeParam { - pub proto: Tag, - pub last_update: Option, - pub env: Option, - pub topic: uuid::Uuid, -} - #[async_trait] pub trait ApiRequests { async fn get_connections( @@ -30,7 +24,7 @@ pub trait ApiRequests { } #[async_trait] -impl ApiRequests for AuthService +impl ApiRequests for Service where C: ConnectionBaseOperations + Send + Sync + Clone + 'static, { @@ -41,56 +35,65 @@ where proto: Tag, last_update: Option, ) -> Result<()> { - let id = self.node.uuid; + let topic = Topic::Init(self.node.uuid); + let env = self.node.env.clone(); - let conn_type_param = ConnTypeParam { + let conn_type_param = ConnType { proto, last_update, - env: None, - topic: id, + env: env.clone(), + topic: topic.clone(), }; - let mut endpoint_url = Url::parse(&endpoint)?; + let mut endpoint_url = Url::parse(&endpoint).map_err(|e| { + tracing::error!("Failed to parse endpoint URL '{}': {}", endpoint, e); + Error::Custom("Invalid API endpoint".to_string()) + })?; + endpoint_url .path_segments_mut() .map_err(|_| Error::Custom("Invalid API endpoint".to_string()))? - .push("connections"); + .push("connections") + .push("sync"); + let endpoint_str = endpoint_url.to_string(); + tracing::debug!("POST /connections/sync Body: {:?}", conn_type_param); + let res = HttpClient::new() - .get(&endpoint_str) - .query(&conn_type_param) - .header("Content-Type", "application/json") - .header("Authorization", format!("Bearer {}", token)) + .post(&endpoint_str) + .header("Authorization", format!("Bearer {}", token.trim())) + .json(&conn_type_param) .send() - .await?; + .await + .map_err(|e| { + tracing::error!("CRITICAL: reqwest send error: {:?}", e); + Error::Custom(format!("HTTP Send Error: {}", e)) + })?; let status = res.status(); let body = res.text().await?; + if status.is_success() { let result: ResponseMessage> = serde_json::from_str(&body)?; let count = match result.response.instance { Instance::Count(count) => count, - _ => { - return Err(Error::Custom("Unexpected instance type".into())); - } + _ => return Err(Error::Custom("Unexpected instance type".into())), }; tracing::debug!( - "Connections Request Accepted for {}: {} Count: {} ", - proto, - status, + "Success: {} connections synced for {} {} {}", count, + proto, + env, + topic ); Ok(()) } else if status == StatusCode::NOT_MODIFIED { - tracing::debug!("Connections Request Accepted for {}: {} ", proto, status,); + tracing::debug!("No updates (304) for {} {} {}", proto, env, topic); Ok(()) } else { - tracing::error!("Connections Request failed: {} - {}", status, body); - Err(Error::Custom(format!( - "Connections Request failed: {} - {}", - status, body - ))) + tracing::error!("Request failed: {} - {}", status, body); + Err(Error::Custom(format!("Status {}: {}", status, body))) } } } diff --git a/src/bin/auth/main.rs b/src/bin/auth/main.rs index 08c5d3b5..b696347b 100644 --- a/src/bin/auth/main.rs +++ b/src/bin/auth/main.rs @@ -1,5 +1,5 @@ -mod auth; mod config; +#[cfg(feature = "email")] mod email; mod filters; mod handlers; @@ -8,17 +8,16 @@ mod http; mod metrics; mod request; mod response; +mod service; mod tasks; -use config::AuthServiceSettings; - -use pony::Settings; - -use pony::{utils::level_from_settings, BANNER, VERSION}; +use config::ServiceSettings; +use fcore::{utils::level_from_settings, Settings, BANNER, VERSION}; fn main() -> Result<(), Box> { + println!(">>> Auth Service {}", VERSION); + println!("{}", BANNER); - println!(">>> {}", VERSION); #[cfg(feature = "debug")] console_subscriber::init(); @@ -28,14 +27,13 @@ fn main() -> Result<(), Box> { .expect("required config path as an argument"); println!("Config file {}", config_path); - let settings = AuthServiceSettings::new(config_path); + let settings = ServiceSettings::from_file(config_path); settings.validate().expect("Wrong settings file"); println!(">>> Settings: {:?}", settings.clone()); - println!(">>> Version: 0.4.10-dev"); tracing_subscriber::fmt() - .with_env_filter(level_from_settings(&settings.logging.level)) + .with_env_filter(level_from_settings(&settings.service.log_level)) .init(); let num_cpus = std::thread::available_parallelism()?.get(); @@ -53,7 +51,7 @@ fn main() -> Result<(), Box> { .build() .unwrap(); - runtime.block_on(auth::run(settings))?; + runtime.block_on(service::run(settings))?; Ok(()) } diff --git a/src/bin/auth/metrics.rs b/src/bin/auth/metrics.rs index 8110f79d..b917ccfb 100644 --- a/src/bin/auth/metrics.rs +++ b/src/bin/auth/metrics.rs @@ -1,8 +1,8 @@ -use pony::{ConnectionBaseOperations, HasMetrics, MetricBuffer, Node}; +use fcore::{ConnectionBaseOperations, HasMetrics, MetricBuffer, Node}; -use super::auth::AuthService; +use super::service::Service; -impl HasMetrics for AuthService +impl HasMetrics for Service where C: ConnectionBaseOperations + Send + Sync + Clone + 'static, { diff --git a/src/bin/auth/request.rs b/src/bin/auth/request.rs index 2602b6e6..2ce47a3c 100644 --- a/src/bin/auth/request.rs +++ b/src/bin/auth/request.rs @@ -1,6 +1,8 @@ -use pony::Code; use serde::{Deserialize, Serialize}; +use fcore::Code; + +#[cfg(feature = "email")] #[derive(Debug, Deserialize)] pub struct Trial { pub email: String, diff --git a/src/bin/auth/auth.rs b/src/bin/auth/service.rs similarity index 77% rename from src/bin/auth/auth.rs rename to src/bin/auth/service.rs index d97c465b..b8587bbf 100644 --- a/src/bin/auth/auth.rs +++ b/src/bin/auth/service.rs @@ -1,26 +1,27 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::path::Path; use std::sync::Arc; -use tokio::signal; -use tokio::sync::broadcast; -use tokio::sync::RwLock; use tokio::task::JoinHandle; -use tokio::time::sleep; -use tokio::time::Duration; +use tokio::time::{sleep, Duration}; +use tokio::{ + signal, + sync::{broadcast, RwLock}, +}; use warp::Filter; -use pony::http::filters as my_filters; -use pony::{ApiAccessConfig, NodeConfig}; -use pony::{ - BaseConnection as Connection, ConnectionBaseOperations, Connections, MetricBuffer, Node, - Publisher, Result, SnapshotManager, Subscriber, +use fcore::{ + http::filters as my_filters, ApiAccessConfig, BaseConnection as Connection, + ConnectionBaseOperations, Connections, MetricBuffer, Node, NodeConfig, Publisher, Result, + SnapshotManager, Subscriber, Tag, Topic, }; -use super::config::AuthServiceSettings; +use super::config::ServiceSettings; +#[cfg(feature = "email")] use super::email::EmailStore; use super::filters; -use super::handlers::{activate_key_handler, auth_handler}; -use super::handlers::{tg_trial_handler, trial_handler}; +#[cfg(feature = "email")] +use super::handlers::trial_handler; +use super::handlers::{activate_key_handler, auth_handler, tg_trial_handler}; use super::http::{ApiRequests, HttpClient}; use super::request; use super::tasks::Tasks; @@ -35,7 +36,7 @@ pub const PROTOS: [&str; 5] = [ pub const DEFAULT_DAYS: i64 = 1; -pub struct AuthService +pub struct Service where C: ConnectionBaseOperations + Send + Sync + Clone + 'static, { @@ -43,14 +44,16 @@ where pub metrics: Arc, pub node: Node, pub subscriber: Subscriber, + #[cfg(feature = "email")] pub email_store: EmailStore, pub http_client: HttpClient, pub api: ApiAccessConfig, pub listen: Ipv4Addr, pub port: u16, + pub origin: String, } -impl AuthService +impl Service where C: ConnectionBaseOperations + Send + Sync + Clone + 'static + std::fmt::Display, { @@ -58,10 +61,11 @@ where metrics: Arc, node: Node, subscriber: Subscriber, - email_store: EmailStore, + #[cfg(feature = "email")] email_store: EmailStore, http_client: HttpClient, api: ApiAccessConfig, listen: (Ipv4Addr, u16), + origin: String, ) -> Self { let memory = Arc::new(RwLock::new(Connections::default())); Self { @@ -69,19 +73,21 @@ where metrics, node, subscriber, + #[cfg(feature = "email")] email_store, http_client, api, listen: listen.0, port: listen.1, + origin, } } - pub async fn start_auth_server(&self) { + pub async fn start_server(&self) { let health_check = warp::path("health-check").map(|| "Server OK"); let cors = warp::cors() - .allow_origin(self.email_store.web_host.as_str()) + .allow_origin(self.origin.as_str()) .allow_methods(vec!["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"]) .allow_headers(vec!["Authorization", "Content-Type"]) .max_age(86400) @@ -89,11 +95,14 @@ where tracing::debug!("CORS: {:?}", cors.clone()); + #[cfg(feature = "email")] let email_store = self.email_store.clone(); + let memory = self.memory.clone(); let http_client = self.http_client.clone(); let api = self.api.clone(); + #[cfg(feature = "email")] let trial_route = warp::post() .and(warp::path("trial")) .and(warp::body::json::()) @@ -124,58 +133,60 @@ where let routes = health_check .or(auth_route) - .or(trial_route) .or(tg_trial_route) .or(activate_route); + #[cfg(feature = "email")] + let routes = routes.or(trial_route); + warp::serve(routes.with(cors)) .run(SocketAddr::new(IpAddr::V4(self.listen), self.port)) .await; } } -pub async fn run(settings: AuthServiceSettings) -> Result<()> { +pub async fn run(settings: ServiceSettings) -> Result<()> { let mut tasks: Vec> = vec![]; let (shutdown_tx, _) = broadcast::channel::<()>(1); let node_config = NodeConfig::from_raw(settings.node.clone()); - let node = Node::new(node_config?, None, None, None, None); + let node = Node::new(node_config?, None, None); - let subscriber = Subscriber::new( - &settings.zmq.endpoint, - &settings.node.uuid, - &settings.node.env, - ); + let topic_init: Topic = settings.node.uuid.into(); - let email_store = EmailStore::new( - settings.auth.email_file.clone(), - settings.smtp.clone(), - settings.auth.email_sign_token.clone(), - settings.auth.web_host.clone(), + let subscriber = Subscriber::new( + &settings.service.updates_endpoint_zmq, + vec![topic_init, Topic::Auth], ); + #[cfg(feature = "email")] + let email_store = EmailStore::new(settings.smtp.clone()); + #[cfg(feature = "email")] email_store.load_trials().await?; - let http_client = HttpClient::new(); - let metric_publisher = Publisher::connect(&settings.metrics.publisher).await; + let http_client = HttpClient::new(); let metrics = MetricBuffer { batch: parking_lot::Mutex::new(Vec::new()), - publisher: metric_publisher, + publisher: Publisher::connect(&settings.metrics.publisher).await?, }; - let auth = Arc::new(AuthService::::new( + let auth_service = Arc::new(Service::::new( Arc::new(metrics), node, - subscriber, + subscriber?, + #[cfg(feature = "email")] email_store, http_client, settings.api.clone(), - (settings.auth.listen, settings.auth.port), + (settings.service.listen, settings.service.port), + settings.service.origin.clone(), )); - let snapshot_manager = - SnapshotManager::new(settings.clone().auth.snapshot_path, auth.memory.clone()); + let snapshot_manager = SnapshotManager::new( + settings.clone().service.snapshot_path, + auth_service.memory.clone(), + ); let snapshot_timestamp = if Path::new(&snapshot_manager.snapshot_path).exists() { match snapshot_manager.load_snapshot().await { @@ -207,11 +218,11 @@ pub async fn run(settings: AuthServiceSettings) -> Result<()> { let snapshot_handle = tokio::spawn(async move { tracing::info!( "Running snapshot task, interval {}", - settings.auth.snapshot_interval + settings.service.snapshot_interval ); let mut interval = tokio::time::interval(std::time::Duration::from_secs( - settings.auth.snapshot_interval, + settings.service.snapshot_interval, )); loop { interval.tick().await; @@ -230,11 +241,11 @@ pub async fn run(settings: AuthServiceSettings) -> Result<()> { tracing::info!("ZMQ listener starting..."); let zmq_task = tokio::spawn({ - let auth = auth.clone(); + let auth_service = auth_service.clone(); let mut shutdown = shutdown_tx.subscribe(); async move { tokio::select! { - _ = auth.run_subscriber() => {}, + _ = auth_service.run_subscriber() => {}, _ = shutdown.recv() => {}, } } @@ -246,15 +257,15 @@ pub async fn run(settings: AuthServiceSettings) -> Result<()> { { let settings = settings.clone(); - let auth = auth.clone(); + let auth_service = auth_service.clone(); loop { let api_token = settings.api.token.clone(); - match auth + match auth_service .get_connections( settings.api.endpoint.clone(), api_token, - pony::Tag::Hysteria2, + Tag::Hysteria2, snapshot_timestamp, ) .await @@ -272,27 +283,27 @@ pub async fn run(settings: AuthServiceSettings) -> Result<()> { { let mut shutdown = shutdown_tx.subscribe(); - let auth = auth.clone(); + let auth_service = auth_service.clone(); - let auth_handle = tokio::spawn(async move { + let service_handle = tokio::spawn(async move { tokio::select! { - _ = auth.start_auth_server() => {}, + _ = auth_service.start_server() => {}, _ = shutdown.recv() => {}, } }); - tasks.push(auth_handle); + tasks.push(service_handle); }; tracing::info!("Running metrics task"); - let auth_for_collect = auth.clone(); + let service_to = auth_service.clone(); let metrics_handle = tokio::spawn({ let mut shutdown = shutdown_tx.subscribe(); async move { loop { tokio::select! { _ = sleep(Duration::from_secs(settings.metrics.interval)) => { - auth_for_collect.collect_metrics().await; + service_to.collect_metrics().await; }, _ = shutdown.recv() => break, } @@ -300,7 +311,7 @@ pub async fn run(settings: AuthServiceSettings) -> Result<()> { } }); - let auth_for_flush = auth.clone(); + let service_to = auth_service.clone(); tracing::info!("Running flush metrics task"); let metrics_flush_handle = tokio::spawn({ let mut shutdown = shutdown_tx.subscribe(); @@ -308,7 +319,7 @@ pub async fn run(settings: AuthServiceSettings) -> Result<()> { loop { tokio::select! { _ = sleep(Duration::from_secs(settings.metrics.interval + 2)) => { - auth_for_flush.metrics.flush_to_zmq().await; + service_to.metrics.flush_to_zmq().await; }, _ = shutdown.recv() => break, } diff --git a/src/bin/auth/tasks.rs b/src/bin/auth/tasks.rs index da3ac705..65c82be3 100644 --- a/src/bin/auth/tasks.rs +++ b/src/bin/auth/tasks.rs @@ -1,16 +1,13 @@ use async_trait::async_trait; -use rkyv::AlignedVec; -use rkyv::Infallible; +use rkyv::{AlignedVec, Deserialize, Infallible}; use tokio::time::Duration; -use pony::{ +use fcore::{ Action, BaseConnection as Connection, ConnectionBaseOperations, ConnectionStorageBaseOperations, Error, Message, Metrics, Proto, Result, Topic, }; -use rkyv::Deserialize; - -use super::auth::AuthService; +use super::service::Service; #[async_trait] pub trait Tasks { @@ -20,13 +17,13 @@ pub trait Tasks { } #[async_trait] -impl Tasks for AuthService +impl Tasks for Service where C: ConnectionBaseOperations + Send + Sync + Clone + 'static + From, { async fn run_subscriber(&self) -> Result<()> { let sub = self.subscriber.clone(); - assert!(self.subscriber.topics.contains(&"all".to_string())); + let node_uuid = self.node.uuid; loop { let Some((topic_bytes, payload_bytes)) = sub.recv().await else { @@ -34,64 +31,69 @@ where continue; }; - let topic_str = std::str::from_utf8(&topic_bytes).unwrap_or(""); - tracing::debug!("SUB: Topic string: {:?}", topic_str); - tracing::debug!("SUB: Payload {} bytes", payload_bytes.len()); + let topic_str = std::str::from_utf8(&topic_bytes) + .map_err(|_| Error::Custom("Invalid UTF8 topic".into()))?; - match Topic::from_raw(topic_str) { - Topic::Init(uuid) if uuid != self.subscriber.topics[0] => { - tracing::warn!("SUB: Skipping init for another node: {}", uuid); + let topic = match topic_str.parse::() { + Ok(t) => t, + Err(e) => { + tracing::error!("SUB: Failed to parse topic '{}': {}", topic_str, e); continue; } - Topic::Updates(env) if env != self.subscriber.topics[1] => { - tracing::warn!("SUB: Skipping update for another env: {}", env); + }; + + tracing::debug!("SUB: Received topic: {:?}", topic); + + match &topic { + Topic::Auth => { + tracing::debug!("SUB: Processing Auth message"); + } + + Topic::Metrics => { + tracing::trace!("SUB: Ignoring Metrics topic"); continue; } - Topic::Unknown(raw) => { - tracing::warn!("SUB: Unknown topic: {}", raw); + + Topic::Updates(env) => { + tracing::trace!("SUB: Ignoring update for env: {}", env); continue; } - Topic::All => { - tracing::debug!("SUB: Message for 'All' topic received"); + + Topic::Init(uuid) if uuid != &node_uuid => { + tracing::trace!("SUB: Skipping init for another node: {}", uuid); + continue; } - topic => { - tracing::debug!("SUB: Accepted topic: {:?}", topic); + _ => { + tracing::debug!("SUB: Accepted for processing: {:?}", topic); } } if payload_bytes.is_empty() { - tracing::warn!("SUB: Empty payload, skipping"); continue; } - let mut aligned = AlignedVec::new(); - aligned.extend_from_slice(&payload_bytes); + let messages: Option> = { + let mut aligned = AlignedVec::new(); + aligned.extend_from_slice(&payload_bytes); - let archived = match rkyv::check_archived_root::>(&aligned) { - Ok(a) => a, - Err(e) => { - tracing::error!("SUB: Invalid rkyv root: {:?}", e); - tracing::error!("SUB: Payload bytes (hex) = {}", hex::encode(payload_bytes)); - continue; + match rkyv::check_archived_root::>(&aligned) { + Ok(archived) => archived.deserialize(&mut Infallible).ok(), + Err(e) => { + tracing::error!("SUB: Invalid rkyv root: {:?}", e); + None + } } }; - match archived.deserialize(&mut Infallible) { - Ok(messages) => { - if let Err(err) = self.handle_messages_batch(messages).await { - tracing::error!("SUB: Failed to handle messages: {}", err); - } - } - Err(err) => { - tracing::error!("SUB: Failed to deserialize messages: {}", err); - tracing::error!("SUB: Payload bytes (hex) = {}", hex::encode(payload_bytes)); + if let Some(msgs) = messages { + if let Err(err) = self.handle_messages_batch(msgs).await { + tracing::error!("SUB: Failed to handle messages: {}", err); } } - tokio::time::sleep(Duration::from_millis(10)).await; + tokio::time::sleep(Duration::from_millis(1)).await; } } - async fn handle_messages_batch(&self, messages: Vec) -> Result<()> { let mut mem = self.memory.write().await; diff --git a/src/bin/agent/config.rs b/src/bin/node/config.rs similarity index 64% rename from src/bin/agent/config.rs rename to src/bin/node/config.rs index 1b636ae5..a78470ba 100644 --- a/src/bin/agent/config.rs +++ b/src/bin/node/config.rs @@ -1,62 +1,29 @@ use serde::Deserialize; -use pony::Result; - -use pony::{ - ApiAccessConfig, LoggingConfig, MetricsTxConfig, MtprotoConfig, NodeConfigRaw, Settings, - ZmqSubscriberConfig, -}; +use fcore::{ApiAccessConfig, MetricsTxConfig, NodeConfigRaw, Result, Settings}; fn default_disabled() -> bool { false } -#[derive(Clone, Default, Debug, Deserialize)] -pub struct WgConfig { - #[serde(default = "default_disabled")] - pub enabled: bool, - pub path: String, -} - -#[derive(Clone, Default, Debug, Deserialize)] -pub struct H2Config { - #[serde(default = "default_disabled")] - pub enabled: bool, - pub path: String, -} - -#[derive(Clone, Debug, Deserialize, Default)] -pub struct XrayConfig { - #[serde(default = "default_disabled")] - pub enabled: bool, - pub xray_config_path: String, -} - -#[derive(Clone, Debug, Deserialize, Default)] -pub struct AgentConfig { - #[serde(default = "default_disabled")] - pub local: bool, - pub snapshot_interval: u64, - pub snapshot_path: String, +fn default_log_level() -> String { + "debug".to_string() } #[derive(Clone, Debug, Deserialize)] -pub struct AgentSettings { +pub struct ServiceSettings { #[serde(default)] - pub logging: LoggingConfig, - #[serde(default)] - pub agent: AgentConfig, + pub service: ServiceConfig, + #[cfg(feature = "xray")] #[serde(default)] pub xray: XrayConfig, + #[cfg(feature = "wireguard")] #[serde(default)] pub wg: WgConfig, #[serde(default)] pub h2: H2Config, #[serde(default)] pub mtproto: MtprotoConfig, - #[serde(default)] - pub zmq: ZmqSubscriberConfig, - #[serde(default)] pub node: NodeConfigRaw, #[serde(default)] pub api: ApiAccessConfig, @@ -64,9 +31,47 @@ pub struct AgentSettings { pub metrics: MetricsTxConfig, } -impl Settings for AgentSettings { +impl Settings for ServiceSettings { fn validate(&self) -> Result<()> { - self.zmq.clone().validate()?; Ok(()) } } + +#[derive(Clone, Debug, Deserialize, Default)] +pub struct ServiceConfig { + #[serde(default = "default_log_level")] + pub log_level: String, + pub snapshot_interval: u64, + pub snapshot_path: String, + pub updates_endpoint_zmq: String, +} + +#[cfg(feature = "xray")] +#[derive(Clone, Debug, Deserialize, Default)] +pub struct XrayConfig { + #[serde(default = "default_disabled")] + pub enabled: bool, + pub path: String, +} + +#[derive(Clone, Default, Debug, Deserialize)] +pub struct H2Config { + #[serde(default = "default_disabled")] + pub enabled: bool, + pub path: String, +} + +#[cfg(feature = "wireguard")] +#[derive(Clone, Default, Debug, Deserialize)] +pub struct WgConfig { + #[serde(default = "default_disabled")] + pub enabled: bool, + pub path: String, +} + +#[derive(Clone, Default, Debug, Deserialize)] +pub struct MtprotoConfig { + #[serde(default = "default_disabled")] + pub enabled: bool, + pub path: String, +} diff --git a/src/bin/agent/http.rs b/src/bin/node/http.rs similarity index 67% rename from src/bin/agent/http.rs rename to src/bin/node/http.rs index 7b0bc069..046156da 100644 --- a/src/bin/agent/http.rs +++ b/src/bin/node/http.rs @@ -1,23 +1,18 @@ use async_trait::async_trait; -use reqwest::Client as HttpClient; -use reqwest::StatusCode; -use reqwest::Url; +use reqwest::{Client as HttpClient, StatusCode, Url}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::net::Ipv4Addr; -use super::agent::Agent; +use fcore::{ + http::{ + request::ConnType, + response::{Instance, InstanceWithId, ResponseMessage}, + }, + ConnectionBaseOperations, Env, Error, Inbound, NodeType, Result, Tag, Topic, +}; -use pony::http::response::{Instance, InstanceWithId, ResponseMessage}; -use pony::{ConnectionBaseOperations, Env, Error, Inbound, NodeType, Result, Tag}; - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct ConnTypeParam { - pub proto: Tag, - pub last_update: Option, - pub env: Option, - pub topic: uuid::Uuid, -} +use crate::node::Node; #[derive(Clone, Debug, Deserialize, Serialize)] pub struct NodeRequest { @@ -37,7 +32,7 @@ pub struct NodeRequest { #[async_trait] pub trait ApiRequests { async fn register_node(&self, _endpoint: String, _token: String) -> Result<()>; - async fn get_connections( + async fn sync_connections( &self, endpoint: String, token: String, @@ -47,43 +42,52 @@ pub trait ApiRequests { } #[async_trait] -impl ApiRequests for Agent +impl ApiRequests for Node where C: ConnectionBaseOperations + Send + Sync + Clone + 'static, { - async fn get_connections( + async fn sync_connections( &self, endpoint: String, token: String, proto: Tag, last_update: Option, ) -> Result<()> { - let node = self.node.clone(); + let topic = Topic::Init(self.node.uuid); + let env = self.node.env.clone(); - let id = node.uuid; - let env = node.env; - - let conn_type_param = ConnTypeParam { + let req = ConnType { proto, last_update, - env: Some(env), - topic: id, + env: env.clone(), + topic: topic.clone(), }; - let mut endpoint_url = Url::parse(&endpoint)?; + let mut endpoint_url = Url::parse(&endpoint).map_err(|e| { + tracing::error!("Failed to parse endpoint URL '{}': {}", endpoint, e); + Error::Custom("Invalid API endpoint".to_string()) + })?; + endpoint_url .path_segments_mut() .map_err(|_| Error::Custom("Invalid API endpoint".to_string()))? - .push("connections"); + .push("connections") + .push("sync"); + let endpoint_str = endpoint_url.to_string(); + tracing::debug!("POST /connections/sync Body: {:?}", req); + let res = HttpClient::new() - .get(&endpoint_str) - .query(&conn_type_param) - .header("Content-Type", "application/json") - .header("Authorization", format!("Bearer {}", token)) + .post(&endpoint_str) + .header("Authorization", format!("Bearer {}", token.trim())) + .json(&req) .send() - .await?; + .await + .map_err(|e| { + tracing::error!("CRITICAL: reqwest send error: {:?}", e); + Error::Custom(format!("HTTP Send Error: {}", e)) + })?; let status = res.status(); let body = res.text().await?; @@ -92,32 +96,22 @@ where let result: ResponseMessage> = serde_json::from_str(&body)?; let count = match result.response.instance { Instance::Count(count) => count, - _ => { - return Err(Error::Custom("Unexpected instance type".into())); - } + _ => return Err(Error::Custom("Unexpected instance type".into())), }; tracing::debug!( - "Message: {}. Connections Request Accepted for {}: {} Count: {} ", - result.message, - proto, - result.status, + "Success: {} connections synced for {} - {} - {}", count, + topic, + env, + proto ); Ok(()) } else if status == StatusCode::NOT_MODIFIED { - tracing::debug!("Connections Request Accepted for {}: {} ", proto, status,); + tracing::debug!("No updates (304) for {} {} {}", topic.clone(), env, proto); Ok(()) } else { - tracing::error!( - "Connections Request failed for {}: {} - {}", - proto, - status, - body - ); - Err(Error::Custom(format!( - "Connections Request failed for {}: {} - {}", - proto, status, body - ))) + tracing::error!("Request failed: {} - {}", status, body); + Err(Error::Custom(format!("Status {}: {}", status, body))) } } diff --git a/src/bin/agent/main.rs b/src/bin/node/main.rs similarity index 64% rename from src/bin/agent/main.rs rename to src/bin/node/main.rs index 4d6939d8..85932c3c 100644 --- a/src/bin/agent/main.rs +++ b/src/bin/node/main.rs @@ -1,20 +1,18 @@ -use tracing::info; - -use pony::{utils::level_from_settings, Settings, BANNER, VERSION}; - -mod agent; mod config; mod http; -pub(crate) mod metrics; +mod metrics; +mod node; mod snapshot; +#[cfg(feature = "xray")] mod stats; mod tasks; -use crate::config::AgentSettings; +use config::ServiceSettings; +use fcore::{utils::level_from_settings, Settings, BANNER, VERSION}; fn main() -> Result<(), Box> { + println!(">>> Node {}", VERSION); println!("{}", BANNER); - println!(">>> {}", VERSION); #[cfg(feature = "debug")] console_subscriber::init(); @@ -24,20 +22,22 @@ fn main() -> Result<(), Box> { .expect("required config path as an argument"); println!("Config file {}", config_path); - let settings = AgentSettings::new(config_path); + let settings = ServiceSettings::from_file(config_path); settings.validate().expect("Wrong settings file"); + println!(">>> Settings: {:?}", settings.clone()); tracing_subscriber::fmt() - .with_env_filter(level_from_settings(&settings.logging.level)) + .with_env_filter(level_from_settings(&settings.service.log_level)) .init(); let num_cpus = std::thread::available_parallelism()?.get(); let worker_threads = if num_cpus <= 1 { 1 } else { num_cpus * 2 }; - info!( + tracing::info!( "🧠 CPU cores: {}, configured worker threads: {}", - num_cpus, worker_threads + num_cpus, + worker_threads ); let runtime = tokio::runtime::Builder::new_multi_thread() @@ -46,7 +46,7 @@ fn main() -> Result<(), Box> { .build() .unwrap(); - runtime.block_on(agent::run(settings))?; + runtime.block_on(node::run(settings))?; Ok(()) } diff --git a/src/bin/agent/metrics.rs b/src/bin/node/metrics.rs similarity index 88% rename from src/bin/agent/metrics.rs rename to src/bin/node/metrics.rs index ffe7023d..235c76c8 100644 --- a/src/bin/agent/metrics.rs +++ b/src/bin/node/metrics.rs @@ -1,7 +1,11 @@ -use super::agent::Agent; -use pony::{ConnectionBaseOperations, HasMetrics, MetricBuffer, Node, Prefix, StatsOp, Tag}; +use super::node::Node; -impl HasMetrics for Agent +#[cfg(feature = "xray")] +use fcore::{Prefix, StatsOp, Tag}; + +use fcore::{ConnectionBaseOperations, HasMetrics, MetricBuffer, Node as MemNode}; + +impl HasMetrics for Node where C: ConnectionBaseOperations + Send + Sync + Clone + 'static, { @@ -9,23 +13,29 @@ where &self.metrics } - fn node_settings(&self) -> &Node { + fn node_settings(&self) -> &MemNode { &self.node } } +#[cfg(any(feature = "xray", feature = "wireguard"))] #[async_trait::async_trait] pub trait BusinessMetrics { + #[cfg(feature = "xray")] async fn collect_inbound_metrics(&self); + #[cfg(feature = "xray")] async fn collect_user_metrics(&self); + #[cfg(feature = "wireguard")] async fn collect_wg_metrics(&self); } +#[cfg(any(feature = "xray", feature = "wireguard"))] #[async_trait::async_trait] -impl BusinessMetrics for Agent +impl BusinessMetrics for Node where C: ConnectionBaseOperations + Send + Sync + Clone + 'static, { + #[cfg(feature = "xray")] async fn collect_inbound_metrics(&self) { let node_uuid = self.node.uuid; let base_tags = self.node.get_base_tags(); @@ -65,6 +75,7 @@ where } } + #[cfg(feature = "xray")] async fn collect_user_metrics(&self) { let node_uuid = self.node.uuid; let base_tags = self.node.get_base_tags(); @@ -104,6 +115,7 @@ where } } + #[cfg(feature = "wireguard")] async fn collect_wg_metrics(&self) { let wg_client = match &self.wg_client { Some(c) => c, diff --git a/src/bin/agent/agent.rs b/src/bin/node/node.rs similarity index 63% rename from src/bin/agent/agent.rs rename to src/bin/node/node.rs index 09631e07..dddc4d10 100644 --- a/src/bin/agent/agent.rs +++ b/src/bin/node/node.rs @@ -1,8 +1,8 @@ -use pony::WireguardServerConfig; use std::path::Path; use std::sync::Arc; use tokio::signal; use tokio::sync::broadcast; +#[cfg(feature = "xray")] use tokio::sync::Mutex; use tokio::sync::RwLock; use tokio::task::JoinHandle; @@ -11,43 +11,52 @@ use tokio::time::Duration; use tracing::{debug, error, info, warn}; -use pony::{ - measure_time, BaseConnection as Connection, ConnectionBaseOperations, Connections, - MetricBuffer, Node, Publisher, Result, SnapshotManager, Subscriber, Tag, WgApi, XrayClient, - XrayHandlerClient, XrayStatsClient, +#[cfg(feature = "xray")] +use fcore::{XrayClient, XrayHandlerClient, XraySettings, XrayStatsClient}; + +#[cfg(feature = "wireguard")] +use fcore::{WgApi, WireguardServerConfig, WireguardSettings}; + +use fcore::{ + utils::measure_time, BaseConnection as Connection, ConnectionBaseOperations, Connections, + MetricBuffer, Node as MemNode, Publisher, Result, SnapshotManager, Subscriber, Tag, Topic, }; -use pony::{H2Settings, HysteriaServerConfig, NodeConfig, WireguardSettings, XraySettings}; +use fcore::{H2Settings, Hysteria2Settings, MtprotoSettings, NodeConfig, Settings}; -use super::config::AgentSettings; +use super::config::ServiceSettings; use super::http::ApiRequests; +#[cfg(any(feature = "xray", feature = "wireguard"))] use super::snapshot::SnapshotRestore; use super::tasks::Tasks; -pub struct Agent +pub struct Node where C: ConnectionBaseOperations + Send + Sync + Clone + 'static, { pub memory: Arc>>, - pub node: Node, + pub node: MemNode, pub metrics: Arc, pub subscriber: Subscriber, - pub xray_stats_client: Option>>, - pub xray_handler_client: Option>>, + #[cfg(feature = "xray")] + pub stats_client: Option>>, + #[cfg(feature = "xray")] + pub handler_client: Option>>, + #[cfg(feature = "wireguard")] pub wg_client: Option, } -impl Agent +impl Node where C: ConnectionBaseOperations + Send + Sync + Clone + 'static, { pub fn new( - node: Node, + node: MemNode, subscriber: Subscriber, metrics: Arc, - xray_stats_client: Option>>, - xray_handler_client: Option>>, - wg_client: Option, + #[cfg(feature = "xray")] stats_client: Option>>, + #[cfg(feature = "xray")] handler_client: Option>>, + #[cfg(feature = "wireguard")] wg_client: Option, ) -> Self { let memory = Arc::new(RwLock::new(Connections::default())); Self { @@ -55,20 +64,24 @@ where node, metrics, subscriber, - xray_stats_client, - xray_handler_client, + #[cfg(feature = "xray")] + stats_client, + #[cfg(feature = "xray")] + handler_client, + #[cfg(feature = "wireguard")] wg_client, } } } -pub async fn run(settings: AgentSettings) -> Result<()> { +pub async fn run(settings: ServiceSettings) -> Result<()> { let mut tasks: Vec> = vec![]; let (shutdown_tx, _) = broadcast::channel::<()>(1); // Init Xray - let (xray_config, xray_stats_client, xray_handler_client) = if settings.xray.enabled { - let config = match XraySettings::new(&settings.xray.xray_config_path) { + #[cfg(feature = "xray")] + let (xray_config, stats_client, handler_client) = if settings.xray.enabled { + let config = match XraySettings::from_file(&settings.xray.path) { Ok(config) => { info!( "Xray Config: Successfully read Xray config file: {:?}", @@ -98,6 +111,7 @@ pub async fn run(settings: AgentSettings) -> Result<()> { }; // Init Wireguard + #[cfg(feature = "wireguard")] let (wg_client, wg_config) = if settings.wg.enabled { let row_config = WireguardServerConfig::from_file(&settings.wg.path)?; let wg: WireguardSettings = row_config.try_into()?; @@ -116,7 +130,7 @@ pub async fn run(settings: AgentSettings) -> Result<()> { // Init Hysteria2 let h2_config = if settings.h2.enabled { - match HysteriaServerConfig::from_file(&settings.h2.path) { + match Hysteria2Settings::from_file(&settings.h2.path) { Ok(cfg) => { if let Err(e) = cfg.validate() { error!("Hysteria2 config validation failed: {}", e); @@ -142,53 +156,68 @@ pub async fn run(settings: AgentSettings) -> Result<()> { // Init Mtproto let mtproto_config = if settings.mtproto.enabled { - Some(settings.mtproto.clone()) + Some(MtprotoSettings::from_file(&settings.mtproto.path)) } else { None }; let node_config = NodeConfig::from_raw(settings.node.clone()); - debug!("NODE {:?} ", node_config); - let node = Node::new( + let node = MemNode::new( node_config?, + #[cfg(feature = "xray")] xray_config, - wg_config.clone(), + #[cfg(feature = "wireguard")] + wg_config, h2_config, mtproto_config, ); - let zmq_endpoint = settings.zmq.endpoint.clone(); - let subscriber = Subscriber::new(&zmq_endpoint, &node.uuid, &node.env.to_string()); + let topic_init: Topic = settings.node.env.clone().into(); + let topic_updates: Topic = settings.node.uuid.into(); - let metric_publisher = Publisher::connect(&settings.metrics.publisher).await; + let topics = vec![topic_updates, topic_init]; + + tracing::debug!("Topics to connect {:?}", topics); + let subscriber = Subscriber::new(&settings.service.updates_endpoint_zmq, topics)?; + let metric_publisher = Publisher::connect(&settings.metrics.publisher).await?; let metrics = MetricBuffer { batch: parking_lot::Mutex::new(Vec::new()), publisher: metric_publisher, }; - let agent = Arc::new(Agent::::new( + let node = Arc::new(Node::::new( node.clone(), subscriber, Arc::new(metrics), - xray_stats_client.clone(), - xray_handler_client.clone(), + #[cfg(feature = "xray")] + stats_client.clone(), + #[cfg(feature = "xray")] + handler_client.clone(), + #[cfg(feature = "wireguard")] wg_client.clone(), )); - let snapshot_path = settings.agent.snapshot_path.clone(); - let snapshot_manager = SnapshotManager::new(snapshot_path, agent.memory.clone()); + let snapshot_path = settings.service.snapshot_path.clone(); + let snapshot_manager = SnapshotManager::new(snapshot_path, node.memory.clone()); let snapshot_timestamp = if Path::new(&snapshot_manager.snapshot_path).exists() { match snapshot_manager.load_snapshot().await { Ok(Some(timestamp)) => { + #[cfg(feature = "wireguard")] + if let Err(e) = snapshot_manager.restore_wg_connections(wg_client).await { + error!("Couldn't restore connections from memory, {}", e); + } + + #[cfg(feature = "xray")] if let Err(e) = snapshot_manager - .restore_connections(agent.xray_handler_client.clone(), wg_client) + .restore_xray_connections(handler_client) .await { error!("Couldn't restore connections from memory, {}", e); } + let count = snapshot_manager.len().await; info!( "Loaded {} connections from snapshot with ts {}", @@ -210,59 +239,59 @@ pub async fn run(settings: AgentSettings) -> Result<()> { warn!("No snapshot found, starting fresh"); None }; + { + tokio::spawn(async move { + info!( + "Running snapshot task, interval {}", + settings.service.snapshot_interval + ); - tokio::spawn(async move { - info!( - "Running snapshot task, interval {}", - settings.agent.snapshot_interval - ); - - let mut interval = tokio::time::interval(std::time::Duration::from_secs( - settings.agent.snapshot_interval, - )); - - loop { - interval.tick().await; - if let Err(e) = measure_time(snapshot_manager.create_snapshot(), "Snapshot").await { - error!("Failed to create snapshot: {}", e); - } else { - let count = snapshot_manager.len().await; - debug!( - "Connections snapshot saved successfully; {} Connections", - count - ); - } - } - }); + let mut interval = tokio::time::interval(std::time::Duration::from_secs( + settings.service.snapshot_interval, + )); - { - info!("ZMQ listener starting..."); - let zmq_task = tokio::spawn({ - let agent = agent.clone(); - let mut shutdown = shutdown_tx.subscribe(); - async move { - tokio::select! { - _ = agent.run_subscriber() => {}, - _ = shutdown.recv() => {}, + loop { + interval.tick().await; + if let Err(e) = measure_time(snapshot_manager.create_snapshot(), "Snapshot").await { + error!("Failed to create snapshot: {}", e); + } else { + let count = snapshot_manager.len().await; + debug!( + "Connections snapshot saved successfully; {} Connections", + count + ); } } }); - tasks.push(zmq_task); - tokio::time::sleep(std::time::Duration::from_millis(500)).await; + { + info!("ZMQ listener starting..."); + let zmq_task = tokio::spawn({ + let node = node.clone(); + let mut shutdown = shutdown_tx.subscribe(); + async move { + tokio::select! { + _ = node.run_subscriber() => {}, + _ = shutdown.recv() => {}, + } + } + }); + tasks.push(zmq_task); + + tokio::time::sleep(std::time::Duration::from_millis(500)).await; - if !settings.agent.local { { let settings = settings.clone(); let node = node.clone(); loop { - match agent + match node .register_node(settings.api.endpoint.clone(), settings.api.token.clone()) .await { Ok(_) => { let tags: Vec<_> = node + .node .inbounds .keys() .filter(|k| !matches!(k, Tag::Hysteria2)) // Hysteria2 uses external auth provider @@ -270,14 +299,13 @@ pub async fn run(settings: AgentSettings) -> Result<()> { .collect(); for tag in tags { - agent - .get_connections( - settings.api.endpoint.clone(), - settings.api.token.clone(), - *tag, - snapshot_timestamp, - ) - .await? + node.sync_connections( + settings.api.endpoint.clone(), + settings.api.token.clone(), + *tag, + snapshot_timestamp, + ) + .await? } break; } @@ -288,19 +316,19 @@ pub async fn run(settings: AgentSettings) -> Result<()> { } } }; - } - }; + }; + } info!("Running metrics task"); let metrics_handle: JoinHandle<()> = tokio::spawn({ - let agent = agent.clone(); + let node = node.clone(); let mut shutdown = shutdown_tx.subscribe(); async move { loop { tokio::select! { _ = sleep(Duration::from_secs(settings.metrics.interval)) => { - agent.collect_metrics().await; + node.collect_metrics().await; }, _ = shutdown.recv() => { @@ -314,13 +342,13 @@ pub async fn run(settings: AgentSettings) -> Result<()> { info!("Running flush metrics task"); let metrics_flush_handle: JoinHandle<()> = tokio::spawn({ - let agent = agent.clone(); + let node = node.clone(); let mut shutdown = shutdown_tx.subscribe(); async move { loop { tokio::select! { _ = sleep(Duration::from_secs(settings.metrics.interval+3)) => { - agent.metrics.flush_to_zmq().await; + node.metrics.flush_to_zmq().await; }, _ = shutdown.recv() => { diff --git a/src/bin/node/snapshot.rs b/src/bin/node/snapshot.rs new file mode 100644 index 00000000..d92fe9d4 --- /dev/null +++ b/src/bin/node/snapshot.rs @@ -0,0 +1,127 @@ +#[cfg(any(feature = "xray", feature = "wireguard"))] +use rkyv::Archive; +#[cfg(feature = "xray")] +use std::sync::Arc; +#[cfg(feature = "xray")] +use tokio::sync::Mutex; + +#[cfg(feature = "wireguard")] +use fcore::WgApi; + +#[cfg(feature = "xray")] +use fcore::{XrayHandlerActions, XrayHandlerClient}; + +#[cfg(any(feature = "xray", feature = "wireguard"))] +use fcore::{Error, Result, Tag}; + +#[cfg(any(feature = "xray", feature = "wireguard"))] +use fcore::{ConnectionBaseOperations, Connections, SnapshotManager}; + +#[cfg(any(feature = "xray", feature = "wireguard"))] +#[async_trait::async_trait] +pub trait SnapshotRestore { + #[cfg(feature = "wireguard")] + async fn restore_wg_connections(&self, wg_client: Option) -> Result<()>; + #[cfg(feature = "xray")] + async fn restore_xray_connections( + &self, + xray_client: Option>>, + ) -> Result<()>; +} + +#[cfg(any(feature = "xray", feature = "wireguard"))] +#[async_trait::async_trait] +impl SnapshotRestore for SnapshotManager> +where + C: Archive + Send + Sync + Clone + 'static + ConnectionBaseOperations, +{ + #[cfg(feature = "wireguard")] + async fn restore_wg_connections(&self, wg_client: Option) -> Result<()> { + let mem = self.memory.read().await; + + if mem.is_empty() { + return Err(Error::Custom("Empty snapshot".into())); + } + + let conns: Vec<(uuid::Uuid, C)> = mem + .iter() + .filter(|(_, conn)| conn.get_proto().proto() == Tag::Wireguard) + .map(|(id, conn)| (*id, conn.clone())) + .collect(); + + drop(mem); + + for (conn_id, conn) in conns { + let wg_client = wg_client.clone(); + + tokio::spawn(async move { + if let Some(wg) = conn.get_wireguard() { + if let Some(api) = wg_client.as_ref() { + if let Ok(pubkey) = &wg.keys.pubkey() { + if let Err(e) = api.create(pubkey, wg.address.clone()) { + tracing::error!( + "Failed to restore WireGuard connection {}: {}", + conn_id, + e + ); + } + } + } + } + }); + } + Ok(()) + } + #[cfg(feature = "xray")] + async fn restore_xray_connections( + &self, + xray_client: Option>>, + ) -> Result<()> { + let mem = self.memory.read().await; + + if mem.is_empty() { + return Err(Error::Custom("Empty snapshot".into())); + } + + let conns: Vec<(uuid::Uuid, C)> = mem + .iter() + .filter(|(_, conn)| { + let p = conn.get_proto().proto(); + matches!( + p, + Tag::VlessTcpReality + | Tag::VlessGrpcReality + | Tag::VlessXhttpReality + | Tag::Vmess + | Tag::Shadowsocks + ) + }) + .map(|(id, conn)| (*id, conn.clone())) + .collect(); + + drop(mem); + + for (conn_id, conn) in conns { + let xray_clone = xray_client.clone(); + tokio::spawn(async move { + if let Some(client) = xray_clone { + let proto = conn.get_proto().proto(); + + let password = if proto == Tag::Shadowsocks { + conn.get_password() + } else { + None + }; + + if let Err(e) = client.create(&conn_id, proto, password).await { + tracing::error!("Failed to restore Xray {}: {}", conn_id, e); + } else { + tracing::debug!("Restored connection {}", conn_id); + } + } + }); + } + + Ok(()) + } +} diff --git a/src/bin/agent/stats.rs b/src/bin/node/stats.rs similarity index 94% rename from src/bin/agent/stats.rs rename to src/bin/node/stats.rs index 14d80784..af08f8e5 100644 --- a/src/bin/agent/stats.rs +++ b/src/bin/node/stats.rs @@ -1,16 +1,16 @@ use tonic::{Code, Request, Status}; -use pony::proto::xray::api::app::stats::command::{GetStatsRequest, GetStatsResponse}; +use fcore::proto::xray::api::app::stats::command::{GetStatsRequest, GetStatsResponse}; -use pony::{ +use fcore::{ ConnectionBaseOperations, ConnectionStat, InboundStat, Prefix, Stat, StatKind, StatsOp, Tag, XrayConnOperation, }; -use super::agent::Agent; +use super::node::Node; #[async_trait::async_trait] -impl StatsOp for Agent +impl StatsOp for Node where C: ConnectionBaseOperations + Send + Sync + Clone + 'static, { @@ -20,7 +20,7 @@ where stat_type: Stat, reset: bool, ) -> Result { - if let Some(client) = &self.xray_stats_client { + if let Some(client) = &self.stats_client { let mut stats_client = client.lock().await; let base_name = match prefix { @@ -127,7 +127,7 @@ where } async fn conn_count(&self, inbound: Tag) -> Result, Status> { - if let Some(client) = &self.xray_handler_client { + if let Some(client) = &self.handler_client { let mut handler_client = client.lock().await; handler_client .conn_count_op(inbound) diff --git a/src/bin/agent/tasks.rs b/src/bin/node/tasks.rs similarity index 73% rename from src/bin/agent/tasks.rs rename to src/bin/node/tasks.rs index 2a796bc2..12e1a496 100644 --- a/src/bin/agent/tasks.rs +++ b/src/bin/node/tasks.rs @@ -1,20 +1,22 @@ use async_trait::async_trait; use futures::future::try_join_all; -use rkyv::AlignedVec; -use rkyv::Deserialize; -use rkyv::Infallible; +use rkyv::{AlignedVec, Deserialize, Infallible}; use tokio::time::Duration; +#[cfg(feature = "xray")] use tonic::Status; -use pony::{ - Action, BaseConnection as Connection, ConnectionBaseOperations, - ConnectionStorageBaseOperations, Message, Metrics, Proto, StatsOp, Tag, Topic, - XrayHandlerActions, -}; -use pony::{Error, Result}; +use fcore::{Action, BaseConnection as Connection, Message, Metrics, Topic}; +#[cfg(any(feature = "xray", feature = "wireguard"))] +use fcore::{ConnectionStorageBaseOperations, Proto, Tag}; +use fcore::{Error, Result}; +#[cfg(feature = "xray")] +use fcore::{StatsOp, XrayHandlerActions}; -use super::agent::Agent; +use fcore::ConnectionBaseOperations; + +#[cfg(any(feature = "xray", feature = "wireguard"))] use super::metrics::BusinessMetrics; +use super::node::Node; #[async_trait] pub trait Tasks { @@ -25,13 +27,14 @@ pub trait Tasks { } #[async_trait] -impl Tasks for Agent +impl Tasks for Node where C: ConnectionBaseOperations + Send + Sync + Clone + 'static + From, { async fn run_subscriber(&self) -> Result<()> { let sub = self.subscriber.clone(); - assert!(self.subscriber.topics.contains(&"all".to_string())); + let node_uuid = self.node.uuid; + let node_env = &self.node.env; loop { let Some((topic_bytes, payload_bytes)) = sub.recv().await else { @@ -39,61 +42,64 @@ where continue; }; - let topic_str = std::str::from_utf8(&topic_bytes).unwrap_or(""); - tracing::debug!("SUB: Topic string: {:?}", topic_str); - tracing::debug!("SUB: Payload {} bytes", payload_bytes.len()); + let topic_str = std::str::from_utf8(&topic_bytes) + .map_err(|_| Error::Custom("Invalid UTF8 topic".into()))?; - match Topic::from_raw(topic_str) { - Topic::Init(uuid) if uuid != self.subscriber.topics[0] => { - tracing::warn!("SUB: Skipping init for another node: {}", uuid); + let topic = match topic_str.parse::() { + Ok(t) => t, + Err(e) => { + tracing::error!("SUB: Failed to parse topic '{}': {}", topic_str, e); continue; } - Topic::Updates(env) if env != self.subscriber.topics[1] => { - tracing::warn!("SUB: Skipping update for another env: {}", env); + }; + + tracing::debug!("SUB: Received topic: {:?}", topic); + + match &topic { + Topic::Auth | Topic::Metrics => { + tracing::trace!("SUB: Ignoring unhandled topic: {:?}", topic); continue; } - Topic::Unknown(raw) => { - tracing::warn!("SUB: Unknown topic: {}", raw); + + Topic::Init(uuid) if uuid != &node_uuid => { + tracing::trace!("SUB: Skipping init for another node: {}", uuid); continue; } - Topic::All => { - tracing::debug!("SUB: Message for 'All' topic received"); + + Topic::Updates(env) if env != node_env => { + tracing::trace!("SUB: Skipping update for another env: {}", env); + continue; } - topic => { - tracing::debug!("SUB: Accepted topic: {:?}", topic); + + _ => { + tracing::debug!("SUB: Accepted for processing: {:?}", topic); } } if payload_bytes.is_empty() { - tracing::warn!("SUB: Empty payload, skipping"); continue; } - let mut aligned = AlignedVec::new(); - aligned.extend_from_slice(&payload_bytes); + let messages: Option> = { + let mut aligned = AlignedVec::new(); + aligned.extend_from_slice(&payload_bytes); - let archived = match rkyv::check_archived_root::>(&aligned) { - Ok(a) => a, - Err(e) => { - tracing::error!("SUB: Invalid rkyv root: {:?}", e); - tracing::error!("SUB: Payload bytes (hex) = {}", hex::encode(payload_bytes)); - continue; + match rkyv::check_archived_root::>(&aligned) { + Ok(archived) => archived.deserialize(&mut Infallible).ok(), + Err(e) => { + tracing::error!("SUB: Invalid rkyv root: {:?}", e); + None + } } }; - match archived.deserialize(&mut Infallible) { - Ok(messages) => { - if let Err(err) = self.handle_messages_batch(messages).await { - tracing::error!("SUB: Failed to handle messages: {}", err); - } - } - Err(err) => { - tracing::error!("SUB: Failed to deserialize messages: {}", err); - tracing::error!("SUB: Payload bytes (hex) = {}", hex::encode(payload_bytes)); + if let Some(msgs) = messages { + if let Err(err) = self.handle_messages_batch(msgs).await { + tracing::error!("SUB: Failed to handle messages: {}", err); } } - tokio::time::sleep(Duration::from_millis(10)).await; + tokio::time::sleep(Duration::from_millis(1)).await; } } @@ -113,9 +119,11 @@ where async fn handle_message(&self, msg: Message) -> Result<()> { match msg.action { Action::Create | Action::Update => { + #[cfg(any(feature = "xray", feature = "wireguard"))] let conn_id: uuid::Uuid = msg.conn_id; match msg.tag { + #[cfg(feature = "wireguard")] Tag::Wireguard => { let wg = msg .wg @@ -161,7 +169,7 @@ where } return Ok(()); } - + #[cfg(feature = "xray")] Tag::VlessTcpReality | Tag::VlessGrpcReality | Tag::VlessXhttpReality @@ -173,7 +181,7 @@ where msg.subscription_id, ); - let client = self.xray_handler_client.as_ref().ok_or_else(|| { + let client = self.handler_client.as_ref().ok_or_else(|| { Error::Grpc(Box::new(Status::unavailable("Xray handler unavailable"))) })?; @@ -195,6 +203,7 @@ where return Ok(()); } + #[cfg(feature = "xray")] Tag::Shadowsocks => { if let Some(password) = msg.password { let proto = Proto::new_ss(&password); @@ -204,7 +213,7 @@ where msg.subscription_id, ); - let client = self.xray_handler_client.as_ref().ok_or_else(|| { + let client = self.handler_client.as_ref().ok_or_else(|| { Error::Grpc(Box::new(Status::unavailable( "Xray handler unavailable", ))) @@ -232,17 +241,19 @@ where )); } } - Tag::Hysteria2 => { - return Err(Error::Custom("Hysteria2 is not supported".into())) + _ => { + return Err(Error::Custom( + "Is not supported, or built without support the proto".into(), + )) } - Tag::Mtproto => return Err(Error::Custom("Mtproto is not supported".into())), } } - Action::Delete => { let tag = msg.tag; + #[cfg(any(feature = "xray", feature = "wireguard"))] let conn_id = msg.conn_id; match tag { + #[cfg(feature = "wireguard")] Tag::Wireguard => { let wg_api = self .wg_client @@ -262,12 +273,13 @@ where return Ok(()); } + #[cfg(feature = "xray")] Tag::VlessTcpReality | Tag::VlessGrpcReality | Tag::VlessXhttpReality | Tag::Vmess | Tag::Shadowsocks => { - let client = self.xray_handler_client.as_ref().ok_or_else(|| { + let client = self.handler_client.as_ref().ok_or_else(|| { Error::Grpc(Box::new(Status::unavailable("Xray handler unavailable"))) })?; @@ -286,14 +298,17 @@ where return Ok(()); } - Tag::Hysteria2 => { - return Err(Error::Custom("Hysteria2 is not supported".into())) + + _ => { + return Err(Error::Custom( + "Is not supported, or built without support the proto".into(), + )) } - Tag::Mtproto => return Err(Error::Custom("Mtproto is not supported".into())), } } Action::ResetStat => { + #[cfg(feature = "xray")] self.reset(&msg.conn_id) .await .map_err(|e| Error::Custom(format!("Couldn't reset stat: {}", e)))?; @@ -310,12 +325,12 @@ where self.loadavg().await; self.memory().await; self.disk_usage().await; - - if self.xray_stats_client.is_some() { + #[cfg(feature = "xray")] + if self.stats_client.is_some() { self.collect_inbound_metrics().await; self.collect_user_metrics().await; } - + #[cfg(feature = "wireguard")] if self.wg_client.is_some() { self.collect_wg_metrics().await; } diff --git a/src/bin/utils.rs b/src/bin/utils.rs deleted file mode 100644 index 3418a8c7..00000000 --- a/src/bin/utils.rs +++ /dev/null @@ -1,90 +0,0 @@ -use clap::{Parser, Subcommand}; -use pony::{Message, Publisher}; -use rkyv::to_bytes; -use std::{fs, path::PathBuf}; - -#[derive(Parser)] -#[command(name = "utils")] -#[command(about = "Helper tool for generating and sending ZMQ messages", long_about = None)] -struct Cli { - #[command(subcommand)] - command: Commands, -} - -#[derive(Subcommand)] -enum Commands { - /// Generate rkyv .bin message from JSON - Gen { - #[arg(short, long)] - input: PathBuf, - #[arg(short, long)] - output: PathBuf, - }, - - /// Send a .bin rkyv message to ZMQ - Send { - #[arg(short, long)] - topic: String, - #[arg(short, long)] - input: PathBuf, - - /// Publisher address, e.g. tcp://127.0.0.1:3000 - #[arg(short, long, default_value = "tcp://127.0.0.1:3000")] - addr: String, - }, - - /// Generate and send in one go - All { - #[arg(short, long)] - input: PathBuf, - #[arg(short, long)] - bin: PathBuf, - #[arg(short, long)] - topic: String, - - /// Publisher address, e.g. tcp://127.0.0.1:3000 - #[arg(short, long, default_value = "tcp://127.0.0.1:3000")] - addr: String, - }, -} - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - let cli = Cli::parse(); - - match cli.command { - Commands::Gen { input, output } => { - let json = fs::read_to_string(input)?; - let msg: Message = serde_json::from_str(&json)?; - let bytes = to_bytes::<_, 1024>(&msg)?; - fs::write(&output, &bytes)?; - println!("✅ rkyv message written to {:?}", output); - } - - Commands::Send { topic, input, addr } => { - let bytes = fs::read(input)?; - let publisher = Publisher::new(&addr).await; - publisher.send_binary(&topic, &bytes).await?; - println!("✅ rkyv message sent to topic: {}", topic); - } - - Commands::All { - input, - bin, - topic, - addr, - } => { - let json = fs::read_to_string(&input)?; - let msg: Message = serde_json::from_str(&json)?; - let bytes = to_bytes::<_, 1024>(&msg)?; - fs::write(&bin, &bytes)?; - println!("✅ [Step 1] Message written to {:?}", bin); - - let publisher = Publisher::new(&addr).await; - publisher.send_binary(&topic, &bytes).await?; - println!("✅ [Step 2] Message sent to topic: {}", topic); - } - } - - Ok(()) -} diff --git a/src/config/h2.rs b/src/config/h2.rs index 2a5caaa7..47d7eea0 100644 --- a/src/config/h2.rs +++ b/src/config/h2.rs @@ -5,7 +5,7 @@ use std::fs::File; use std::io::Read; #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct HysteriaServerConfig { +pub struct Hysteria2Settings { pub listen: Option, pub acme: Option, pub auth: Option, @@ -13,6 +13,17 @@ pub struct HysteriaServerConfig { pub masquerade: Option, } +impl Hysteria2Settings { + pub fn from_file(path: &str) -> Result { + let mut file = File::open(path)?; + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + + let config: Hysteria2Settings = serde_yaml::from_str(&contents)?; + Ok(config) + } +} + #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct H2AuthInfo { pub auth_type: String, @@ -48,17 +59,6 @@ pub struct Masquerade { pub r#type: String, } -impl HysteriaServerConfig { - pub fn from_file(path: &str) -> anyhow::Result { - let mut file = File::open(path)?; - let mut contents = String::new(); - file.read_to_string(&mut contents)?; - - let config: HysteriaServerConfig = serde_yaml::from_str(&contents)?; - Ok(config) - } -} - #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct H2Settings { pub host: String, @@ -77,7 +77,7 @@ pub struct H2Obfs { pub password: String, } -impl HysteriaServerConfig { +impl Hysteria2Settings { pub fn validate(&self) -> Result<()> { if self.listen.is_none() { return Err(Error::Custom("Hysteria2: listen is required".into())); @@ -92,10 +92,10 @@ impl HysteriaServerConfig { } } -impl TryFrom for H2Settings { +impl TryFrom for H2Settings { type Error = Error; - fn try_from(server: HysteriaServerConfig) -> std::result::Result { + fn try_from(server: Hysteria2Settings) -> std::result::Result { let listen = server .listen .ok_or_else(|| Error::Custom("Hysteria2: listen missing".into()))?; diff --git a/src/config/inbound.rs b/src/config/inbound.rs index 2a6117f4..8f97c59e 100644 --- a/src/config/inbound.rs +++ b/src/config/inbound.rs @@ -147,11 +147,10 @@ pub struct Settings { impl Settings { pub fn validate(&self) -> Result<()> { - // ToDo validate xray config Ok(()) } - pub fn new(file_path: &str) -> Result { + pub fn from_file(file_path: &str) -> Result { let mut file = File::open(file_path)?; let mut contents = String::new(); diff --git a/src/config/mod.rs b/src/config/mod.rs index b001f7fb..c4d96f73 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,5 +1,6 @@ pub(crate) mod clash; pub(crate) mod h2; pub(crate) mod inbound; +pub(crate) mod mtproto; pub(crate) mod settings; pub(crate) mod wireguard; diff --git a/src/config/mtproto.rs b/src/config/mtproto.rs new file mode 100644 index 00000000..2e900114 --- /dev/null +++ b/src/config/mtproto.rs @@ -0,0 +1,21 @@ +use serde::{Deserialize, Serialize}; + +use crate::Settings; + +#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct SecretEntry { + pub key: String, + pub label: String, +} + +#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct MtprotoSettings { + pub port: u16, + pub secret: Vec, +} + +impl Settings for MtprotoSettings { + fn validate(&self) -> crate::Result<()> { + Ok(()) + } +} diff --git a/src/config/settings.rs b/src/config/settings.rs index ce2a6481..3680186c 100644 --- a/src/config/settings.rs +++ b/src/config/settings.rs @@ -1,16 +1,11 @@ -use default_net::{get_default_interface, get_interfaces}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize}; use std::env; use std::fs; use std::net::Ipv4Addr; use uuid::Uuid; use crate::error::{Error, Result}; -use crate::memory::node::Type; - -fn default_disabled() -> bool { - false -} +use crate::memory::{env::Env, node::Type}; #[derive(Clone, Debug, Deserialize, Default)] pub struct ApiAccessConfig { @@ -24,14 +19,9 @@ pub struct MetricsTxConfig { pub interval: u64, } -#[derive(Clone, Debug, Deserialize, Default)] -pub struct LoggingConfig { - pub level: String, -} - #[derive(Clone, Debug, Deserialize)] pub struct NodeConfig { - pub env: String, + pub env: Env, pub hostname: String, pub default_interface: String, pub address: Ipv4Addr, @@ -43,12 +33,12 @@ pub struct NodeConfig { pub r#type: Type, } -#[derive(Clone, Debug, Deserialize, Default)] +#[derive(Clone, Debug, Deserialize)] pub struct NodeConfigRaw { - pub env: String, + pub env: Env, pub hostname: Option, - pub default_interface: Option, - pub address: Option, + pub default_interface: String, + pub address: Ipv4Addr, pub uuid: Uuid, pub label: String, pub max_bandwidth_bps: i64, @@ -70,73 +60,11 @@ impl NodeConfig { raw.hostname.unwrap() }; - let (address, interface) = if let Some(user_address) = raw.address { - let interface = if let Some(ref interface_name) = raw.default_interface { - let interfaces = get_interfaces(); - if let Some(_interface) = interfaces.iter().find(|i| &i.name == interface_name) { - interface_name.clone() - } else { - return Err(Error::Custom(format!( - "Validation error: Interface {} not found", - interface_name - ))); - } - } else { - match get_default_interface() { - Ok(interface) => interface.name, - Err(e) => { - eprintln!( - "Warning: Cannot get default interface: {}. Using 'default'.", - e - ); - "default".to_string() - } - } - }; - - (user_address, interface) - } else if let Some(ref interface_name) = raw.default_interface { - let interfaces = get_interfaces(); - if let Some(interface) = interfaces.iter().find(|i| &i.name == interface_name) { - match interface.ipv4.first() { - Some(network) => (network.addr, interface_name.to_string()), - None => { - return Err(Error::Custom( - "Validation error: Cannot get IPv4 address for the specified interface" - .into(), - )); - } - } - } else { - return Err(Error::Custom(format!( - "Validation error: Interface {} not found", - interface_name - ))); - } - } else { - match get_default_interface() { - Ok(interface) => { - if interface.ipv4.is_empty() { - return Err(Error::Custom( - "Validation error: Cannot get IPv4 address of default interface".into(), - )); - } else { - (interface.ipv4[0].addr, interface.name) - } - } - Err(e) => { - return Err( - format!("Validation error: Cannot get default interface: {}", e).into(), - ) - } - } - }; - Ok(NodeConfig { env: raw.env, hostname, - default_interface: interface, - address, + default_interface: raw.default_interface, + address: raw.address, uuid: raw.uuid, label: raw.label, max_bandwidth_bps: raw.max_bandwidth_bps, @@ -147,22 +75,6 @@ impl NodeConfig { } } -#[derive(Clone, Debug, Deserialize, Default)] -pub struct ZmqSubscriberConfig { - pub endpoint: String, -} - -impl ZmqSubscriberConfig { - pub fn validate(self) -> Result<()> { - if !self.endpoint.starts_with("tcp://") { - return Err(Error::Custom( - "ZMQ endpoint should start with tcp://".into(), - )); - } - Ok(()) - } -} - pub trait Settings: Sized { fn read_config(config_file: &str) -> Result { let config_str = fs::read_to_string(config_file)?; @@ -170,7 +82,7 @@ pub trait Settings: Sized { Ok(settings) } - fn new(config_file: &str) -> Self + fn from_file(config_file: &str) -> Self where for<'de> Self: Deserialize<'de>, { @@ -182,11 +94,3 @@ pub trait Settings: Sized { fn validate(&self) -> Result<()>; } - -#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)] -pub struct MtprotoConfig { - #[serde(default = "default_disabled")] - pub enabled: bool, - pub port: u16, - pub secret: String, -} diff --git a/src/config/wireguard.rs b/src/config/wireguard.rs index 6a8ee247..90516d50 100644 --- a/src/config/wireguard.rs +++ b/src/config/wireguard.rs @@ -14,7 +14,7 @@ pub struct WireguardServerConfig { } impl WireguardServerConfig { - pub fn from_file(path: &str) -> anyhow::Result { + pub fn from_file(path: &str) -> Result { let contents = std::fs::read_to_string(path)?; let mut private_key = None; @@ -26,7 +26,7 @@ impl WireguardServerConfig { .split('/') .next_back() .and_then(|f| f.split('.').next()) - .ok_or_else(|| anyhow::anyhow!("no interface name"))? + .ok_or_else(|| Error::Custom("no interface name".into()))? .to_string(); for line in contents.lines() { @@ -36,7 +36,7 @@ impl WireguardServerConfig { if let Some((_, value)) = line.split_once('=') { private_key = Some(value.trim().to_string()); } else { - return Err(anyhow::anyhow!("Invalid PrivateKey line")); + return Err(Error::Custom("Invalid PrivateKey line".into())); } } @@ -64,9 +64,9 @@ impl WireguardServerConfig { Ok(Self { interface, - port: port.ok_or_else(|| anyhow::anyhow!("no ListenPort"))?, - private_key: private_key.ok_or_else(|| anyhow::anyhow!("no PrivateKey"))?, - address: address.ok_or_else(|| anyhow::anyhow!("no Address"))?, + port: port.ok_or_else(|| Error::Custom("no ListenPort".into()))?, + private_key: private_key.ok_or_else(|| Error::Custom("no PrivateKey".into()))?, + address: address.ok_or_else(|| Error::Custom("no Address".into()))?, dns: Some(dns), }) } diff --git a/src/error.rs b/src/error.rs index 8a3385d5..f80df3ef 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,3 +1,4 @@ +#[cfg(feature = "wireguard")] use defguard_wireguard_rs::{error::WireguardInterfaceError, net::IpAddrParseError}; use thiserror::Error as ThisError; @@ -21,15 +22,21 @@ pub enum Error { #[error(transparent)] Json(#[from] serde_json::Error), + #[error(transparent)] + Yaml(#[from] serde_yaml::Error), + #[error(transparent)] Join(#[from] tokio::task::JoinError), + #[cfg(feature = "xray")] #[error(transparent)] XrayUri(#[from] tonic::codegen::http::uri::InvalidUri), + #[cfg(feature = "xray")] #[error(transparent)] XrayTransport(#[from] tonic::transport::Error), + #[cfg(feature = "xray")] #[error(transparent)] Grpc(#[from] Box), @@ -39,9 +46,11 @@ pub enum Error { #[error(transparent)] Zmq(#[from] zmq::Error), + #[cfg(feature = "wireguard")] #[error(transparent)] Wireguard(#[from] WireguardInterfaceError), + #[cfg(feature = "wireguard")] #[error(transparent)] IpParseError(#[from] IpAddrParseError), @@ -110,18 +119,13 @@ impl From for Error { } } -impl From for Error { - fn from(err: anyhow::Error) -> Self { - Error::Custom(err.to_string()) - } -} - impl From> for Error { fn from(err: tokio::sync::mpsc::error::SendError) -> Self { Error::Custom(format!("SendError: {:?}", err)) } } +#[cfg(feature = "xray")] impl From for Error { fn from(status: tonic::Status) -> Self { Error::Grpc(Box::new(status)) diff --git a/src/http/mod.rs b/src/http/mod.rs index b4252188..14cde172 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs @@ -4,6 +4,7 @@ use warp::reject::Reject; pub mod filters; pub mod helpers; +pub mod request; pub mod response; #[derive(Debug)] diff --git a/src/http/request.rs b/src/http/request.rs new file mode 100644 index 00000000..041ed79f --- /dev/null +++ b/src/http/request.rs @@ -0,0 +1,12 @@ +use crate::memory::{env::Env, tag::ProtoTag as Tag}; +use crate::zmq::topic::Topic; + +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct ConnType { + pub proto: Tag, + pub last_update: Option, + pub env: Env, + pub topic: Topic, +} diff --git a/src/lib.rs b/src/lib.rs index 88399270..3019b19d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,34 +10,32 @@ pub mod zmq; pub use error::{Error, Result, SyncError}; pub const BANNER: &str = r#" - __ _ - / _| | | - | |_ _ __| | ___ __ - _____ _ | _| '__| |/ / '_ \ - | __ \ (_) | | | | | <| | | | - | |__) | __ ___ |_|_|_| _|_|\_\_| |_| - | ___/ '__| \ \ / / _` |/ __| | | | - | | | | | |\ V / (_| | (__| |_| | - |_|___|_| |_| \_/ \__,_|\___|\__, | - / ____| __/ | - | | ___ _ __ ___ _ __ |___/_ __ _ _ - | | / _ \| '_ ` _ \| '_ \ / _` | '_ \| | | | - | |___| (_) | | | | | | |_) | (_| | | | | |_| | - \_____\___/|_| |_| |_| .__/ \__,_|_| |_|\__, | - | | __/ | - |_| |___/ +// __ _ +// / _| | | +// | |_ _ __| | ___ __ +// _____ _ | _| '__| |/ / '_ \ +// | __ \ (_) | | | | | <| | | | +// | |__) | __ ___|_|_|_| _|_|\_\_| |_| +// | ___/ '__| \ \ / / _` |/ __| | | | +// | | | | | |\ V / (_| | (__| |_| | +// |_|___|_| |_| \_/ \__,_|\___|\__, | +// / ____| __/ / +// | | ___ _ __ ___ _ __ | ___/_ __ _ _ +// | | / _ \| '_ ` _ \| '_ \ / _` | '_ \| | | | +// | |___| (_) | | | | | | |_) | (_| | | | | |_| | +// \_____\___/|_| |_| |_| .__/ \__,_|_| |_|\__, | +// | | __/ / +// |_| |___/ "#; -pub const VERSION: &str = "0.4.11-dev"; +pub const VERSION: &str = "0.5.0-dev"; pub use config::{ clash::InboundClashConfig, - h2::{H2Settings, HysteriaServerConfig}, + h2::{H2Settings, Hysteria2Settings}, inbound::{Inbound, InboundConnLink, Settings as XraySettings}, - settings::{ - ApiAccessConfig, LoggingConfig, MetricsTxConfig, MtprotoConfig, NodeConfig, NodeConfigRaw, - Settings, ZmqSubscriberConfig, - }, + mtproto::MtprotoSettings, + settings::{ApiAccessConfig, MetricsTxConfig, NodeConfig, NodeConfigRaw, Settings}, wireguard::{WireguardServerConfig, WireguardSettings}, }; @@ -76,16 +74,15 @@ pub use metrics::{ storage::{HasMetrics, MetricBuffer, MetricStorage}, MetricEnvelope, Metrics, }; - -pub use proto::{ - wireguard::WgApi, - xray::{ - client::{ - ConnOp as XrayConnOperation, HandlerActions as XrayHandlerActions, - HandlerClient as XrayHandlerClient, StatsClient as XrayStatsClient, XrayClient, - }, - stats::{Prefix, StatsOp}, +#[cfg(feature = "wireguard")] +pub use proto::wireguard::WgApi; +#[cfg(feature = "xray")] +pub use proto::xray::{ + client::{ + ConnOp as XrayConnOperation, HandlerActions as XrayHandlerActions, + HandlerClient as XrayHandlerClient, StatsClient as XrayStatsClient, XrayClient, }, + stats::{Prefix, StatsOp}, }; pub use utils::*; @@ -94,5 +91,5 @@ pub use zmq::{ message::{Action, Message}, publisher::Publisher, subscriber::Subscriber, - Topic, + topic::Topic, }; diff --git a/src/memory/connection/wireguard.rs b/src/memory/connection/wireguard.rs index 613e6537..4dc04aad 100644 --- a/src/memory/connection/wireguard.rs +++ b/src/memory/connection/wireguard.rs @@ -1,6 +1,5 @@ use base64::{engine::general_purpose, Engine}; use serde::{Deserialize, Deserializer, Serialize}; - use std::{ error, fmt, net::{IpAddr, Ipv4Addr, Ipv6Addr}, @@ -11,6 +10,8 @@ use rand::rngs::OsRng; use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize}; use x25519_dalek::{PublicKey, StaticSecret}; +use crate::error::Error; + #[derive( Archive, Clone, Debug, Serialize, Deserialize, PartialEq, RkyvDeserialize, RkyvSerialize, )] @@ -30,21 +31,19 @@ impl Default for Keys { } } -use anyhow::{anyhow, Result}; - impl Keys { - pub fn pubkey(&self) -> Result { + pub fn pubkey(&self) -> Result { Self::derive_pubkey(&self.privkey) } - fn derive_pubkey(private_key_b64: &str) -> Result { - let private_bytes = general_purpose::STANDARD + fn derive_pubkey(private_key_b64: &str) -> Result { + let private_vec = general_purpose::STANDARD .decode(private_key_b64) - .map_err(|e| anyhow!("invalid base64 private key: {}", e))?; + .map_err(|e| Error::Custom(format!("invalid base64 private key: {}", e)))?; - let private_bytes: [u8; 32] = private_bytes + let private_bytes: [u8; 32] = private_vec .try_into() - .map_err(|_| anyhow!("invalid private key length (expected 32 bytes)"))?; + .map_err(|_| Error::Custom("Private key must be exactly 32 bytes".to_string()))?; let secret = StaticSecret::from(private_bytes); let public = PublicKey::from(&secret); @@ -59,8 +58,6 @@ pub enum IpVersion { IPv6, } -/// IP address with CIDR. - #[derive(Archive, Clone, Debug, Serialize, RkyvDeserialize, RkyvSerialize, PartialEq, Eq, Hash)] #[archive(check_bytes)] pub struct IpAddrMask { @@ -224,7 +221,7 @@ impl FromStr for IpAddrMask { IpAddr::V6(_) => 128, }; if cidr > max_cidr { - return Err(IpAddrParseError); + return Err(IpAddrParseError).into(); } Ok(IpAddrMask { address: ip, cidr }) } else { diff --git a/src/memory/env.rs b/src/memory/env.rs index e0261d39..704c7de1 100644 --- a/src/memory/env.rs +++ b/src/memory/env.rs @@ -1,17 +1,44 @@ -use crate::error::Error; use serde::{Deserialize, Serialize}; +use std::borrow::Cow; use std::fmt::Formatter; -#[derive(Hash, Eq, Debug, Clone, PartialEq, Serialize, Deserialize)] +use crate::error::Error; + +#[derive(Hash, Eq, Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum Env { Production, + #[default] Experimental, Dev, Ru, Wl, #[serde(untagged)] - Premium(String), + Custom(String), +} + +impl Env { + pub fn as_bytes(&self) -> Vec { + match self { + Env::Experimental => b"experimental".to_vec(), + Env::Dev => b"dev".to_vec(), + Env::Ru => b"ru".to_vec(), + Env::Wl => b"wl".to_vec(), + Env::Production => b"production".to_vec(), + Env::Custom(id) => format!("custom{}", id).into_bytes(), + } + } + + pub fn as_str(&self) -> Cow<'_, str> { + match self { + Env::Dev => Cow::Borrowed("dev"), + Env::Ru => Cow::Borrowed("ru"), + Env::Wl => Cow::Borrowed("wl"), + Env::Experimental => Cow::Borrowed("experimental"), + Env::Production => Cow::Borrowed("production"), + Env::Custom(name) => Cow::Owned(format!("custom{}", name)), + } + } } impl std::fmt::Display for Env { @@ -22,7 +49,7 @@ impl std::fmt::Display for Env { Env::Wl => write!(f, "wl"), Env::Experimental => write!(f, "experimental"), Env::Production => write!(f, "production"), - Env::Premium(id) => write!(f, "premium{}", id), + Env::Custom(name) => write!(f, "custom{}", name), } } } @@ -39,9 +66,9 @@ impl FromStr for Env { "production" | "prod" => Ok(Env::Production), "ru" => Ok(Env::Ru), "wl" => Ok(Env::Wl), - s if s.starts_with("premium") => { - let id = s.strip_prefix("premium").unwrap_or(s).to_string(); - Ok(Env::Premium(id)) + s if s.starts_with("custom") => { + let name = s.strip_prefix("custom").unwrap_or(s).to_string(); + Ok(Env::Custom(name)) } _ => Err(Error::Custom("Wrong Env string".into())), } @@ -58,3 +85,65 @@ impl From for Env { Env::from(s.as_str()) } } + +#[cfg(test)] +mod tests { + use super::*; + use serde_json; + + #[test] + fn test_env_from_str() { + assert_eq!(Env::from_str("dev").unwrap(), Env::Dev); + assert_eq!(Env::from_str("DEVELOPMENT").unwrap(), Env::Dev); + assert_eq!(Env::from_str("prod").unwrap(), Env::Production); + assert_eq!(Env::from_str("experimental").unwrap(), Env::Experimental); + assert_eq!(Env::from_str("ru").unwrap(), Env::Ru); + } + + #[test] + fn test_custom_parsing() { + assert_eq!( + Env::from_str("custom_env_123").unwrap(), + Env::Custom("_env_123".to_string()) + ); + assert_eq!( + Env::from_str("custom").unwrap(), + Env::Custom("".to_string()) + ); + + let custom = Env::Custom("777".to_string()); + assert_eq!(custom.to_string(), "custom777"); + } + + #[test] + fn test_serde_serialization() { + assert_eq!(serde_json::to_string(&Env::Dev).unwrap(), "\"dev\""); + + let custom_json = serde_json::to_string(&Env::Custom("hehe".to_string())).unwrap(); + assert_eq!(custom_json, "\"hehe\""); + } + + #[test] + fn test_serde_deserialization() { + let dev: Env = serde_json::from_str("\"dev\"").unwrap(); + assert_eq!(dev, Env::Dev); + + let prem: Env = serde_json::from_str("\"some_random_id\"").unwrap(); + assert_eq!(prem, Env::Custom("some_random_id".to_string())); + } + + #[test] + fn test_from_conversions() { + let env: Env = "prod".into(); + assert_eq!(env, Env::Production); + + let env_fail: Env = "unknown_garbage".into(); + assert_eq!(env_fail, Env::Experimental); + } + + #[test] + fn test_error_handling() { + let result = Env::from_str("invalid"); + assert!(result.is_err()); + } +} diff --git a/src/memory/node.rs b/src/memory/node.rs index b8ad0407..a1fb8f65 100644 --- a/src/memory/node.rs +++ b/src/memory/node.rs @@ -13,8 +13,14 @@ use serde::{Deserialize, Serialize}; use super::env::Env; use super::tag::ProtoTag as Tag; use crate::config::h2::H2Settings; -use crate::config::inbound::{Inbound, Settings as XraySettings}; -use crate::config::settings::{MtprotoConfig, NodeConfig}; + +#[cfg(feature = "xray")] +use crate::config::inbound::Settings as XraySettings; + +use crate::config::inbound::Inbound; +use crate::config::mtproto::MtprotoSettings; +use crate::config::settings::NodeConfig; +#[cfg(feature = "wireguard")] use crate::config::wireguard::WireguardSettings; #[derive(Clone, Debug, Deserialize, Serialize, Copy, ToSql, FromSql)] @@ -134,15 +140,16 @@ pub struct Node { impl Node { pub fn new( settings: NodeConfig, - xray_config: Option, - wg_config: Option, + #[cfg(feature = "xray")] xray_config: Option, + #[cfg(feature = "wireguard")] wg_config: Option, h2_config: Option, - mtproto_config: Option, + mtproto_config: Option, ) -> Self { let now = Utc::now(); let mut inbounds: HashMap = HashMap::new(); { + #[cfg(feature = "xray")] if let Some(config) = xray_config { let xray_inbounds = config .inbounds @@ -153,6 +160,7 @@ impl Node { inbounds.extend(xray_inbounds); } + #[cfg(feature = "wireguard")] if let Some(ref config) = wg_config { inbounds.insert( Tag::Wireguard, @@ -182,7 +190,7 @@ impl Node { conn_count: None, wg: None, h2: None, - mtproto_secret: Some(config.secret.clone()), + mtproto_secret: Some(config.secret[0].key.clone()), }, ); } diff --git a/src/metrics/storage.rs b/src/metrics/storage.rs index a831e059..ae0922c8 100644 --- a/src/metrics/storage.rs +++ b/src/metrics/storage.rs @@ -1,10 +1,9 @@ use dashmap::DashMap; use std::collections::{BTreeMap, HashSet, VecDeque}; -use super::MetricEnvelope; -use super::MetricPoint; +use super::{MetricEnvelope, MetricPoint}; use crate::memory::node::Node; -use crate::zmq::publisher::Publisher; +use crate::zmq::{publisher::Publisher, topic::Topic}; pub trait HasMetrics { fn metrics(&self) -> &MetricBuffer; @@ -70,7 +69,7 @@ impl MetricBuffer { if let Err(e) = &self .publisher - .send_binary("metrics", bytes.as_slice()) + .send_binary(&Topic::Metrics, bytes.as_slice()) .await { tracing::error!("Batch publish failed: {}", e); diff --git a/src/proto/mod.rs b/src/proto/mod.rs index 7608e1d5..a0833d16 100644 --- a/src/proto/mod.rs +++ b/src/proto/mod.rs @@ -1,2 +1,4 @@ +#[cfg(feature = "wireguard")] pub(crate) mod wireguard; +#[cfg(feature = "xray")] pub mod xray; diff --git a/src/zmq/mod.rs b/src/zmq/mod.rs index 197fdc43..58bf82b9 100644 --- a/src/zmq/mod.rs +++ b/src/zmq/mod.rs @@ -1,36 +1,4 @@ pub(crate) mod message; pub(crate) mod publisher; pub(crate) mod subscriber; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Topic { - Updates(String), - Init(String), - Unknown(String), - All, -} - -impl Topic { - pub fn from_raw(raw: &str) -> Self { - if uuid::Uuid::parse_str(raw).is_ok() { - Topic::Init(raw.to_string()) - } else if raw == "all" { - Topic::All - } else { - Topic::Updates(raw.to_string()) - } - } - - pub fn as_zmq_topic(&self) -> String { - match self { - Topic::Updates(env) => env.into(), - Topic::Init(uuid) => uuid.into(), - Topic::Unknown(s) => s.clone(), - Topic::All => "all".to_string(), - } - } - - pub fn all(uuid: &uuid::Uuid, env: &str) -> Vec { - vec![uuid.to_string(), env.to_string(), "all".to_string()] - } -} +pub(crate) mod topic; diff --git a/src/zmq/publisher.rs b/src/zmq/publisher.rs index 9f206c6d..d4280197 100644 --- a/src/zmq/publisher.rs +++ b/src/zmq/publisher.rs @@ -4,24 +4,28 @@ use tokio::time::{sleep, Duration}; use zmq; use zmq::Socket; +use crate::{Error, Topic}; + #[derive(Clone)] pub struct Publisher { socket: Arc>, } impl Publisher { - pub async fn bind(endpoint: &str) -> Self { + pub async fn bind(endpoint: &str) -> Result { Self::init(endpoint, true).await } - pub async fn connect(endpoint: &str) -> Self { + pub async fn connect(endpoint: &str) -> Result { Self::init(endpoint, false).await } - async fn init(endpoint: &str, is_bind: bool) -> Self { + async fn init(endpoint: &str, is_bind: bool) -> Result { let context = zmq::Context::new(); let publisher = context.socket(zmq::PUB).expect("Failed to create socket"); + publisher.set_sndhwm(1000)?; + let mut i = 0; loop { let result = if is_bind { @@ -52,19 +56,19 @@ impl Publisher { sleep(Duration::from_millis(1000)).await; - Self { + Ok(Self { socket: Arc::new(Mutex::new(publisher)), - } + }) } - pub async fn new(endpoint: &str) -> Self { + pub async fn new(endpoint: &str) -> Result { Self::bind(endpoint).await } - pub async fn send_binary(&self, topic: &str, payload: &[u8]) -> zmq::Result<()> { + pub async fn send_binary(&self, topic: &Topic, payload: &[u8]) -> zmq::Result<()> { let socket = self.socket.lock().await; - socket.send(topic, zmq::SNDMORE)?; + socket.send(topic.as_str().as_ref(), zmq::SNDMORE)?; socket.send(payload, 0)?; tracing::debug!("PUB: Message sent: {} | {} bytes", topic, payload.len()); diff --git a/src/zmq/subscriber.rs b/src/zmq/subscriber.rs index 8c377d35..a28724f0 100644 --- a/src/zmq/subscriber.rs +++ b/src/zmq/subscriber.rs @@ -1,42 +1,39 @@ use std::sync::Arc; use tokio::sync::Mutex; +use zmq::Error; use zmq::Socket as ZmqSocket; -use super::Topic; +use super::topic::Topic; pub struct Subscriber { socket: Arc>, - pub topics: Vec, + pub topics: Vec, } impl Subscriber { - pub fn new(endpoint: &str, uuid: &uuid::Uuid, env: &str) -> Self { + pub fn new(endpoint: &str, topics: Vec) -> Result { let context = zmq::Context::new(); let socket = context .socket(zmq::SUB) .expect("Failed to create SUB socket"); + tracing::debug!("Connecting SUB {} {:?}", endpoint, topics); socket .connect(endpoint) .expect("Failed to connect SUB socket"); - let topics = vec![ - Topic::Init(uuid.to_string()).as_zmq_topic(), - Topic::Updates(env.to_string()).as_zmq_topic(), - Topic::All.as_zmq_topic(), - ]; - tracing::info!("Subscribed to topics: {:?}", Topic::all(uuid, env)); + tracing::debug!("Subscribed to topics: {:?}", topics); for topic in &topics { socket - .set_subscribe(topic.as_bytes()) + .set_subscribe(&topic.as_bytes()) .expect("Failed to subscribe to topic"); } - Self { + Ok(Self { socket: Arc::new(Mutex::new(socket)), topics, - } + }) } pub async fn recv(&self) -> Option<(Vec, Vec)> { @@ -78,32 +75,26 @@ impl Subscriber { } } - pub fn new_bound(endpoint: &str, topics: Vec) -> Self { + pub fn new_bound(endpoint: &str, topics: Vec) -> Result { let context = zmq::Context::new(); let socket = context .socket(zmq::SUB) .expect("Failed to create SUB socket"); socket.bind(endpoint).expect("Failed to bind SUB socket"); + socket.set_rcvhwm(5000)?; - if topics.is_empty() { + for topic in &topics { socket - .set_subscribe(b"") - .expect("Failed to subscribe to all"); - tracing::info!("Subscribed to all topics (wildcard)"); - } else { - for topic in &topics { - socket - .set_subscribe(topic.as_bytes()) - .expect("Failed to subscribe to topic"); - } - tracing::info!("Subscribed to topics: {:?}", topics); + .set_subscribe(&topic.as_bytes()) + .expect("Failed to subscribe to topic"); } + tracing::debug!("Subscribed to topics: {:?}", topics); - Self { + Ok(Self { socket: Arc::new(Mutex::new(socket)), topics, - } + }) } } diff --git a/src/zmq/topic.rs b/src/zmq/topic.rs new file mode 100644 index 00000000..a0c786b2 --- /dev/null +++ b/src/zmq/topic.rs @@ -0,0 +1,108 @@ +use core::fmt; +use serde::{Deserialize, Serialize}; +use std::borrow::Cow; + +use crate::error::Error; +use crate::memory::env::Env; + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] +pub enum Topic { + Auth, + Metrics, + Updates(Env), + Init(uuid::Uuid), +} + +impl fmt::Display for Topic { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Auth => write!(f, "auth"), + Self::Metrics => write!(f, "metrics",), + Self::Updates(env) => write!(f, "updates-{}", env), + Self::Init(uuid) => write!(f, "init-{}", uuid), + } + } +} + +use std::str::FromStr; + +impl FromStr for Topic { + type Err = Error; + + fn from_str(s: &str) -> Result { + let s = s.to_lowercase(); + + if s == "auth" { + return Ok(Topic::Auth); + } + if s == "metrics" { + return Ok(Topic::Metrics); + } + + if let Some(env_str) = s.strip_prefix("updates-") { + let env = Env::from_str(env_str)?; + return Ok(Topic::Updates(env)); + } + + if let Some(uuid_str) = s.strip_prefix("init-") { + let id = uuid::Uuid::parse_str(uuid_str) + .map_err(|_| Error::Custom("Invalid UUID in topic".into()))?; + return Ok(Topic::Init(id)); + } + + Err(Error::Custom(format!("Unknown topic string: {}", s))) + } +} + +impl TryFrom<&str> for Topic { + type Error = Error; + fn try_from(s: &str) -> Result { + s.parse() + } +} + +impl TryFrom for Topic { + type Error = Error; + + fn try_from(s: String) -> Result { + Topic::try_from(s.as_str()) + } +} + +impl From for Topic { + fn from(u: uuid::Uuid) -> Self { + Topic::Init(u) + } +} + +impl From for Topic { + fn from(e: Env) -> Self { + Topic::Updates(e) + } +} +impl Topic { + pub fn as_string(&self) -> String { + match self { + Topic::Auth => "auth".to_string(), + Topic::Metrics => "metrics".to_string(), + Topic::Updates(s) => format!("updates-{}", s), + Topic::Init(s) => format!("init-{}", s), + } + } + + pub fn as_str(&self) -> Cow<'_, str> { + match self { + Topic::Auth => Cow::Borrowed("auth"), + Topic::Metrics => Cow::Borrowed("metrics"), + Topic::Updates(env) => format!("updates-{}", env).into(), + Topic::Init(uuid) => Cow::Owned(format!("init-{}", uuid)), + } + } + pub fn as_bytes(&self) -> Vec { + match self { + Topic::Auth => b"auth".to_vec(), + Topic::Metrics => b"metrics".to_vec(), + _ => self.as_str().as_bytes().to_vec(), + } + } +}