diff --git a/Cargo.lock b/Cargo.lock index 9a7c05074..6b8903371 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -135,7 +135,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -146,7 +146,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -281,7 +281,7 @@ checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -458,7 +458,7 @@ checksum = "46d07918caa9eeaaf06b7873925c53a61daac173539b4f7715090745e44e4e69" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -502,7 +502,7 @@ dependencies = [ "maybe-owned", "rustix", "rustix-linux-procfs", - "windows-sys 0.61.2", + "windows-sys 0.60.2", "winx", ] @@ -686,7 +686,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -731,7 +731,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -1404,7 +1404,7 @@ checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -1538,7 +1538,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -1728,7 +1728,7 @@ checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -2226,9 +2226,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.14.1" +version = "2.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07aa2048142242915a31d35844fb311e0e53fcca590c3a0a40dcf1b841fa09eb" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" dependencies = [ "equivalent", "hashbrown 0.17.1", @@ -2733,7 +2733,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -2770,7 +2770,7 @@ checksum = "e4e98dc3b890f6c23a0f9d3d491a2823d0dea0fa656302a13dd225fa924112a8" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -3270,7 +3270,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" dependencies = [ "anyhow", - "itertools 0.14.0", + "itertools 0.13.0", "proc-macro2", "quote", "syn 2.0.119", @@ -3302,7 +3302,7 @@ checksum = "1c8d9ca532f185d5d4db7a7c9d51420b452168ea1c2b913953281bd6fe1fcbd0" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -3328,7 +3328,7 @@ dependencies = [ [[package]] name = "pumpkin" -version = "0.1.0-dev+26.2-26.45" +version = "0.1.0+26.2-26.45" dependencies = [ "aes 0.9.3", "arc-swap", @@ -3399,17 +3399,17 @@ dependencies = [ [[package]] name = "pumpkin-api-macros" -version = "0.1.0-dev+26.2-26.45" +version = "0.1.0+26.2-26.45" dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] name = "pumpkin-codecs" -version = "0.1.0-dev+26.2-26.45" +version = "0.1.0+26.2-26.45" dependencies = [ "serde_json", "tracing", @@ -3431,13 +3431,13 @@ dependencies = [ "serde", "serde_json", "serde_repr", - "syn 3.0.4", + "syn 3.0.5", "wit-encoder", ] [[package]] name = "pumpkin-config" -version = "0.1.0-dev+26.2-26.45" +version = "0.1.0+26.2-26.45" dependencies = [ "pumpkin-util", "serde", @@ -3448,7 +3448,7 @@ dependencies = [ [[package]] name = "pumpkin-data" -version = "0.1.0-dev+26.2-26.45" +version = "0.1.0+26.2-26.45" dependencies = [ "crc-fast", "criterion", @@ -3461,7 +3461,7 @@ dependencies = [ [[package]] name = "pumpkin-fuzzer" -version = "0.1.0-dev+26.2-26.45" +version = "0.1.0+26.2-26.45" dependencies = [ "clap", "colored", @@ -3471,7 +3471,7 @@ dependencies = [ [[package]] name = "pumpkin-gametest" -version = "0.1.0-dev+26.2-26.45" +version = "0.1.0+26.2-26.45" dependencies = [ "async-trait", "pumpkin-data", @@ -3486,7 +3486,7 @@ dependencies = [ [[package]] name = "pumpkin-inventory" -version = "0.1.0-dev+26.2-26.45" +version = "0.1.0+26.2-26.45" dependencies = [ "pumpkin-data", "pumpkin-nbt", @@ -3501,19 +3501,19 @@ dependencies = [ [[package]] name = "pumpkin-macros" -version = "0.1.0-dev+26.2-26.45" +version = "0.1.0+26.2-26.45" dependencies = [ "heck", "proc-macro-error2", "proc-macro2", "pumpkin-data", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] name = "pumpkin-nbt" -version = "0.1.0-dev+26.2-26.45" +version = "0.1.0+26.2-26.45" dependencies = [ "bytes", "cesu8", @@ -3528,7 +3528,7 @@ dependencies = [ [[package]] name = "pumpkin-plugin-api" -version = "0.1.0" +version = "0.1.0+26.2-26.45" dependencies = [ "postcard", "serde_json", @@ -3539,7 +3539,7 @@ dependencies = [ [[package]] name = "pumpkin-plugin-runtime" -version = "0.1.0-dev+26.2-26.45" +version = "0.1.0+26.2-26.45" dependencies = [ "futures", "thiserror 2.0.20", @@ -3551,7 +3551,7 @@ dependencies = [ [[package]] name = "pumpkin-plugin-utils" -version = "0.1.0" +version = "0.1.0+26.2-26.45" dependencies = [ "pumpkin-plugin-api", "reqwest", @@ -3566,7 +3566,7 @@ dependencies = [ [[package]] name = "pumpkin-protocol" -version = "0.1.0-dev+26.2-26.45" +version = "0.1.0+26.2-26.45" dependencies = [ "aes 0.9.3", "async-compression", @@ -3591,7 +3591,7 @@ dependencies = [ [[package]] name = "pumpkin-util" -version = "0.1.0-dev+26.2-26.45" +version = "0.1.0+26.2-26.45" dependencies = [ "base64 0.23.1", "bytes", @@ -3614,7 +3614,7 @@ dependencies = [ "serde", "serde_json", "sha2 0.11.0", - "syn 3.0.4", + "syn 3.0.5", "thiserror 2.0.20", "uuid", "webpki-root-certs", @@ -3622,7 +3622,7 @@ dependencies = [ [[package]] name = "pumpkin-world" -version = "0.1.0-dev+26.2-26.45" +version = "0.1.0+26.2-26.45" dependencies = [ "arc-swap", "bitflags 2.13.1", @@ -3671,7 +3671,7 @@ dependencies = [ "libc", "log", "socket2", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -3931,7 +3931,7 @@ checksum = "1c25ef604ac7dd839d44d64648952ea23c97866f124ff671b0ed2cf3ad9bb06e" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -3954,9 +3954,9 @@ dependencies = [ [[package]] name = "rtc" -version = "0.20.4" +version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9005c36795ad076abd36db3ea9ae0275a60395944647d58c1f2bc3e118dddba" +checksum = "66355ae7cad547376873a2ee7968eee1e3727492a241c05bf511b01db4cef077" dependencies = [ "bytes", "hex", @@ -3989,9 +3989,9 @@ dependencies = [ [[package]] name = "rtc-datachannel" -version = "0.20.4" +version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14b61c7b8e9892094cba8a6c7fb8120ae863d32d432b00e560177b3d7ae6d2c6" +checksum = "c1767cfa2571e226e16d1594518c5160d7bef10c6e0fe7a60a8f979051233ea1" dependencies = [ "bytes", "log", @@ -4002,9 +4002,9 @@ dependencies = [ [[package]] name = "rtc-dtls" -version = "0.20.4" +version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c737b1dd17a0ff63884a2f466b9588f0cffadfce5a724c0c368764d7c27f9c74" +checksum = "4eb84feb0331735ab54ba58bfeb15300b517c283c5ee45e9f94d8727e6573ec5" dependencies = [ "aes 0.8.4", "bytecheck", @@ -4035,9 +4035,9 @@ dependencies = [ [[package]] name = "rtc-ice" -version = "0.20.4" +version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c06eeabd250a7693e1e8b28222b78c4a81a7c6ca7fe3cb99bbdff2f6c0ff0ab" +checksum = "9ee1c117265c3460ef2d88b54bdd5048c7ee05d5de948310e9ca053ff5154f74" dependencies = [ "bytes", "crc", @@ -4054,9 +4054,9 @@ dependencies = [ [[package]] name = "rtc-interceptor" -version = "0.20.4" +version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ec2776ab86c0c03c3de8ec742aa13ccc3b2c82317590d0624f251bd37eae3b9" +checksum = "a2a76412e39ae1a8a024be246009ffb5e6300f9703bc7b1ba41a3e0e1c5e63c1" dependencies = [ "log", "rand", @@ -4069,9 +4069,9 @@ dependencies = [ [[package]] name = "rtc-interceptor-derive" -version = "0.20.4" +version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc31da3875839bc18e00997354a582e6decacdb23d39f8c004fc4fe2fd3fcd8b" +checksum = "57385751766059daa81c7badbef27b894c358057be0413bf329875c6950ae125" dependencies = [ "proc-macro2", "quote", @@ -4080,9 +4080,9 @@ dependencies = [ [[package]] name = "rtc-mdns" -version = "0.20.4" +version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cd53120b83321c8b8310cce9512c685d2d6356acef34647a99ea420e6f2249" +checksum = "52e1e0868d134ff0f10cd6ee71ad0d53c4a81a733f9950947eb8806b0a090159" dependencies = [ "bytes", "log", @@ -4093,9 +4093,9 @@ dependencies = [ [[package]] name = "rtc-media" -version = "0.20.4" +version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0411e44fcdc1363487ae0c6a4612ec908b9b09175d05499c5de0b3c81f0b5e5" +checksum = "fdd79fb066099a90edd43dfe5ef296021218def9070ce39dcd0756e309e8945f" dependencies = [ "byteorder", "bytes", @@ -4107,9 +4107,9 @@ dependencies = [ [[package]] name = "rtc-rtcp" -version = "0.20.4" +version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fceaadac17b114140368dd68478d186ccfc293970f79cb8dd28f94dbbc0300be" +checksum = "b515f133b14a119a3862c34b36c807bb985e90dc64515b2283b8e9a177d6ae3f" dependencies = [ "bytes", "rtc-shared", @@ -4117,9 +4117,9 @@ dependencies = [ [[package]] name = "rtc-rtp" -version = "0.20.4" +version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "babb45ac2263340e2bb412d1921aa07ca441ae9b5589477221d1a50d98750ba3" +checksum = "f8a5509cd26a487309ac0d933b100af8976c399c1082009d4d4364a205968127" dependencies = [ "bytes", "memchr", @@ -4130,9 +4130,9 @@ dependencies = [ [[package]] name = "rtc-sctp" -version = "0.20.4" +version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2513391b524b88b041277faba617e0531ad726729861ea8933015393fd68ab05" +checksum = "2f786b8224032e25084a52401fcc28a8b6a4a5b1b1971152ad01113279628861" dependencies = [ "bytes", "crc32c", @@ -4146,9 +4146,9 @@ dependencies = [ [[package]] name = "rtc-sdp" -version = "0.20.4" +version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d136a422965289e3d32d8e110789d8fa2ac669a4474a4084fa1eeac32758ee" +checksum = "6cb942d52e95a0b6b6178374e88e90b069d8aa7078e6e581e1490db2521978e8" dependencies = [ "rand", "rtc-shared", @@ -4157,9 +4157,9 @@ dependencies = [ [[package]] name = "rtc-shared" -version = "0.20.4" +version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce81c72adbce3a2f2ad8e2b97674d2482be83cb3bfd9a153341b04b1a87ca24" +checksum = "393280ae9959b5245df9eb38a5cd02b373a98021599d36111c339a4696bf8488" dependencies = [ "aes 0.8.4", "aes-gcm", @@ -4179,9 +4179,9 @@ dependencies = [ [[package]] name = "rtc-srtp" -version = "0.20.4" +version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b880d5f6d436b3215c7bcfd3fb5ffe2a752fea0236440ccd98b6bf1c3561df8" +checksum = "c77ba0abc312a93840746c27d4d1f16efc71baa9d3838ca9dda65e478e41461d" dependencies = [ "aes 0.8.4", "byteorder", @@ -4198,9 +4198,9 @@ dependencies = [ [[package]] name = "rtc-stun" -version = "0.20.4" +version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06b19c36a200df6071b92a25583a19ab0f9c6d5ad036d8c3ff5d392802cc926e" +checksum = "dc22012b96070d048d16bd6d2b0ca451bc8e920a28fb850b8f9644f21e32a826" dependencies = [ "base64 0.22.1", "bytes", @@ -4217,9 +4217,9 @@ dependencies = [ [[package]] name = "rtc-turn" -version = "0.20.4" +version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebf0b5fbb94085c86be7277c38ae8f1c21ec75ab47ba975be0984409732020dd" +checksum = "55fd9c23215c3e22ae698b423ae86992a89cb0c24440e3e7e2932dcad75aa3f0" dependencies = [ "bytes", "log", @@ -4278,7 +4278,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -4345,7 +4345,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -4514,7 +4514,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -4539,7 +4539,7 @@ checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -4709,7 +4709,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -4793,9 +4793,9 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.4" +version = "3.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f" +checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" dependencies = [ "proc-macro2", "quote", @@ -4851,7 +4851,7 @@ dependencies = [ "fastrand", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -4900,7 +4900,7 @@ checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -5005,7 +5005,7 @@ checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] @@ -5770,9 +5770,9 @@ dependencies = [ [[package]] name = "webrtc" -version = "0.20.4" +version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3daa8f2f6366331ae3275a6c02a855c6fb3faa1d16960498d7daaf61c96e76bd" +checksum = "91ce8fa1c8b8aa8753b7313a26e46e27d973e11945e35f525ac6700062c3b295" dependencies = [ "async-broadcast", "async-channel", @@ -5808,7 +5808,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -6411,7 +6411,7 @@ checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" dependencies = [ "proc-macro2", "quote", - "syn 3.0.4", + "syn 3.0.5", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 0343ef555..e18ef8a99 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -105,7 +105,7 @@ struct_excessive_bools = "allow" unused_async_trait_impl = "allow" [workspace.package] -version = "0.1.0-dev+26.2-26.45" +version = "0.1.0+26.2-26.45" edition = "2024" rust-version = "1.96" license = "GPL-3.0" @@ -147,7 +147,7 @@ serde_json = { version = "1.0", default-features = false, features = ["std"] } tempfile = { version = "3.27.0", default-features = false } proc-macro-error2 = { version = "2", default-features = false } aes = { version = "0.9", default-features = false } -async-compression = { version = "0.4.43", default-features = false } +async-compression = { version = "0.4.44", default-features = false } base64 = { version = "0.23.1", default-features = false, features = ["std"] } bitflags = { version = "2.13.1", default-features = false, features = ["std"] } cesu8 = { version = "1.1", default-features = false } @@ -166,7 +166,7 @@ indexmap = { version = "2.14", default-features = false, features = ["std"] } itertools = { version = "0.15.0", default-features = false, features = ["use_std"] } libc = { version = "0.2", default-features = false } libloading = { version = "0.9", default-features = false, features = ["std"] } -lru = { version = "0.18.3", default-features = false } +lru = { version = "0.18.4", default-features = false } lz4-java-wrc = { version = "0.2.0", default-features = false, features = ["lz4_flex"] } md5 = { version = "0.8", default-features = false } num-bigint = { version = "0.5", default-features = false, features = ["std"] } @@ -226,7 +226,7 @@ wasmtime-wasi-http = { git = "https://github.com/bytecodealliance/wasmtime", rev # needed for interacting with wasmtime-wasi-http - keep in sync hyper = { version = "^1.11", default-features = false } axum = { version = "0.8.9", default-features = false, features = ["http1", "tokio"] } -webrtc = { version = "0.20.3" } +webrtc = { version = "0.20.5" } async-trait = "0.1" wit-bindgen = { version = "0.61", default-features = false, features = ["macros"] } @@ -234,6 +234,6 @@ postcard = { version = "1.1", default-features = false, features = ["alloc"] } tracing-serde-structured = { version = "0.4", default-features = false } wasmparser = { version = "0.258", default-features = false } wasm-encoder = { version = "0.258", default-features = false, features = ["component-model", "std"] } -webpki-root-certs = { version = "1.0.8", default-features = false } +webpki-root-certs = { version = "1.0.9", default-features = false } # ring-only: must match the provider the rest of the tree (wasmtime-wasi-http, rtc) uses rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12", "logging"] } diff --git a/crates/pumpkin-config/src/lib.rs b/crates/pumpkin-config/src/lib.rs index bbb92e3ad..36657b65f 100644 --- a/crates/pumpkin-config/src/lib.rs +++ b/crates/pumpkin-config/src/lib.rs @@ -41,6 +41,10 @@ pub use networking::rcon::RCONConfig; pub use plugins::{PluginOverride, PluginsConfig}; pub use pvp::PVPConfig; pub use server_links::ServerLinksConfig; +pub use telemetry::TelemetryConfig; + +/// Telemetry configuration options. +pub mod telemetry; mod commands; @@ -77,6 +81,9 @@ pub struct PumpkinConfig { /// Advanced and feature-specific configuration settings. #[serde(flatten)] pub advanced: AdvancedConfiguration, + /// Anonymous telemetry configuration settings. + #[serde(default)] + pub telemetry: TelemetryConfig, } impl LoadConfiguration for PumpkinConfig { @@ -87,6 +94,7 @@ impl LoadConfiguration for PumpkinConfig { fn validate(&self) { self.basic.validate(); self.advanced.validate(); + self.telemetry.validate(); let min_vd = NonZero::::MIN; let Some(max_vd) = NonZero::new(64) else { diff --git a/crates/pumpkin-config/src/telemetry.rs b/crates/pumpkin-config/src/telemetry.rs new file mode 100644 index 000000000..537a12ea3 --- /dev/null +++ b/crates/pumpkin-config/src/telemetry.rs @@ -0,0 +1,73 @@ +use serde::{Deserialize, Serialize}; + +/// Telemetry configuration options. +#[derive(Deserialize, Serialize, Clone, Debug)] +#[serde(default)] +pub struct TelemetryConfig { + /// Whether anonymous telemetry is enabled. Default is true. + pub enabled: bool, + /// Custom telemetry backend ingestion endpoint. + pub endpoint: String, + /// Heartbeat interval in seconds (default: 300 seconds / 5 minutes). Minimum 60s. + pub interval_secs: u64, + /// Whether to opt-in to displaying this server in the public community directory. + pub public: bool, + /// Public server name displayed on the analytics dashboard if public is true. + pub server_name: Option, +} + +impl Default for TelemetryConfig { + fn default() -> Self { + Self { + enabled: true, + endpoint: "https://market.pumpkinmc.org/api/v1/rest/telemetry/heartbeat".to_string(), + interval_secs: 300, + public: false, + server_name: None, + } + } +} + +impl TelemetryConfig { + /// Validates telemetry configuration options. + pub const fn validate(&self) {} +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn telemetry_default() { + let default_config = TelemetryConfig::default(); + assert!(default_config.enabled); + assert_eq!( + default_config.endpoint, + "https://market.pumpkinmc.org/api/v1/rest/telemetry/heartbeat" + ); + assert_eq!(default_config.interval_secs, 300); + assert!(!default_config.public); + assert_eq!(default_config.server_name, None); + } + + #[test] + fn telemetry_toml_deserialization() { + let toml_str = r#" + enabled = false + endpoint = "http://localhost:5000/api/v1/rest/telemetry/heartbeat" + interval_secs = 60 + public = true + server_name = "Test SMP" + "#; + + let config: TelemetryConfig = toml::from_str(toml_str).unwrap(); + assert!(!config.enabled); + assert_eq!( + config.endpoint, + "http://localhost:5000/api/v1/rest/telemetry/heartbeat" + ); + assert_eq!(config.interval_secs, 60); + assert!(config.public); + assert_eq!(config.server_name.as_deref(), Some("Test SMP")); + } +} diff --git a/crates/pumpkin-plugin-api/Cargo.toml b/crates/pumpkin-plugin-api/Cargo.toml index 20fd292df..793040a07 100644 --- a/crates/pumpkin-plugin-api/Cargo.toml +++ b/crates/pumpkin-plugin-api/Cargo.toml @@ -1,7 +1,6 @@ [package] name = "pumpkin-plugin-api" -# This should be different from the pumpkin server version. -version = "0.1.0" +version.workspace = true edition.workspace = true license = "MIT OR Apache-2.0" diff --git a/crates/pumpkin-plugin-utils/Cargo.toml b/crates/pumpkin-plugin-utils/Cargo.toml index 4c6911118..ec83c2d8c 100644 --- a/crates/pumpkin-plugin-utils/Cargo.toml +++ b/crates/pumpkin-plugin-utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pumpkin-plugin-utils" -version = "0.1.0" +version.workspace = true edition.workspace = true license = "MIT OR Apache-2.0" description = "Utilities for Pumpkin plugins: licensing, lease caching, and update checks." diff --git a/crates/pumpkin/src/lib.rs b/crates/pumpkin/src/lib.rs index b118726f3..daa022e6e 100644 --- a/crates/pumpkin/src/lib.rs +++ b/crates/pumpkin/src/lib.rs @@ -24,7 +24,7 @@ use crate::net::{lan_broadcast::LANBroadcast, query, rcon::RCONServer}; use crate::plugin::server::server_command::ServerCommandEvent; use crate::server::{Server, ticker::Ticker}; use plugin::server::server_load::{LoadType, ServerLoadEvent}; -use pumpkin_config::{AdvancedConfiguration, BasicConfiguration}; +use pumpkin_config::{AdvancedConfiguration, BasicConfiguration, TelemetryConfig}; use pumpkin_util::text::TextComponent; use pumpkin_util::text::color::{Color, NamedColor}; use rustyline::Editor; @@ -61,6 +61,7 @@ pub mod logging; pub mod net; pub mod plugin; pub mod server; +pub mod telemetry; pub mod world; pub struct LoggingConfig { @@ -248,9 +249,16 @@ impl PumpkinServer { pub async fn new( basic_config: BasicConfiguration, advanced_config: AdvancedConfiguration, + telemetry_config: TelemetryConfig, vanilla_data: VanillaData, ) -> Self { - let server = Server::new(basic_config, advanced_config, vanilla_data).await; + let server = Server::new( + basic_config, + advanced_config, + telemetry_config, + vanilla_data, + ) + .await; #[cfg(target_family = "unix")] adjust_file_descriptor_limit(); @@ -466,6 +474,8 @@ impl PumpkinServer { .fire(&self.server, &mut ServerLoadEvent::new(LoadType::Startup)) .await; + self.server.start_telemetry(); + while !SHOULD_STOP.load(Ordering::Relaxed) { if !self .unified_listener_task(&mut master_client_id, &tasks, &bedrock_clients) diff --git a/crates/pumpkin/src/main.rs b/crates/pumpkin/src/main.rs index a13045d36..c6f94ba27 100644 --- a/crates/pumpkin/src/main.rs +++ b/crates/pumpkin/src/main.rs @@ -120,7 +120,13 @@ async fn main() { } }); - let pumpkin_server = PumpkinServer::new(config.basic, config.advanced, vanilla_data).await; + let pumpkin_server = PumpkinServer::new( + config.basic, + config.advanced, + config.telemetry, + vanilla_data, + ) + .await; let plugin_wait_time = pumpkin_server.init_plugins().await; let time_elapsed = time.elapsed().saturating_sub(plugin_wait_time); diff --git a/crates/pumpkin/src/server/mod.rs b/crates/pumpkin/src/server/mod.rs index 28690a867..6ea63f726 100644 --- a/crates/pumpkin/src/server/mod.rs +++ b/crates/pumpkin/src/server/mod.rs @@ -20,7 +20,7 @@ use crate::{ use arc_swap::ArcSwap; use connection_cache::{CachedBranding, CachedStatus}; use key_store::KeyStore; -use pumpkin_config::{AdvancedConfiguration, BasicConfiguration}; +use pumpkin_config::{AdvancedConfiguration, BasicConfiguration, TelemetryConfig}; use pumpkin_data::dimension::Dimension; use pumpkin_util::permission::PermissionManager; use pumpkin_util::text::color::NamedColor; @@ -70,6 +70,7 @@ use crate::server::scheduler::TaskScheduler; pub struct Server { pub basic_config: BasicConfiguration, pub advanced_config: AdvancedConfiguration, + pub telemetry_config: TelemetryConfig, pub data: VanillaData, @@ -160,6 +161,7 @@ impl Server { pub async fn new( basic_config: BasicConfiguration, advanced_config: AdvancedConfiguration, + telemetry_config: TelemetryConfig, vanilla_data: VanillaData, ) -> Arc { let permission_manager = Arc::new(PermissionManager::new()); @@ -278,6 +280,7 @@ impl Server { let server = Self { basic_config, advanced_config, + telemetry_config, data: vanilla_data, plugin_manager: Arc::new(PluginManager::new(verify_plugin_signatures)), permission_manager, @@ -999,6 +1002,17 @@ impl Server { false } + /// Returns the maximum number of players allowed on the server. + #[must_use] + pub const fn max_players(&self) -> u32 { + self.advanced_config.networking.java.max_players + } + + /// Starts the background telemetry task if enabled in configuration. + pub fn start_telemetry(self: &Arc) { + crate::telemetry::start_telemetry(self.clone()); + } + /// Generates a new container id. pub fn new_container_id(&self) -> u32 { self.container_id.fetch_add(1, Ordering::SeqCst) diff --git a/crates/pumpkin/src/telemetry.rs b/crates/pumpkin/src/telemetry.rs new file mode 100644 index 000000000..d5f61af15 --- /dev/null +++ b/crates/pumpkin/src/telemetry.rs @@ -0,0 +1,350 @@ +use std::path::Path; +use std::sync::Arc; +use std::sync::atomic::Ordering; +use std::time::Duration; + +use pumpkin_config::TelemetryConfig; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +use crate::server::Server; + +/// Filename used to persist the server ID across restarts. +pub const SERVER_ID_FILENAME: &str = ".pumpkin-server-id"; + +/// Information about an active plugin on the server. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct PluginInfo { + /// The name of the plugin. + pub name: String, + /// The version of the plugin. + pub version: String, +} + +/// The telemetry heartbeat payload transmitted to the backend. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct HeartbeatPayload { + /// Pseudonymous UUID identifying this server installation. + pub server_id: Uuid, + /// The server software implementation type (e.g. "pumpkin"). + pub server_type: String, + /// The Pumpkin server software version. + pub server_version: String, + /// The Minecraft game version string. + pub minecraft_version: String, + /// The network protocol version number. + pub protocol_version: i32, + /// Current number of connected players. + pub online_players: usize, + /// Maximum number of players allowed. + pub max_players: u32, + /// Host operating system name. + pub os: String, + /// Host CPU architecture. + pub arch: String, + /// Number of available CPU cores. + pub cpu_cores: usize, + /// CPU model name or brand. + pub cpu_model: String, + /// RAM allocated/used by the system in MiB. + pub ram_allocated_mb: u64, + /// Total physical RAM available to the system in MiB. + pub total_ram_mb: u64, + /// List of active plugins. + pub plugins: Vec, + /// Whether the server has opted into public directory listing. + pub is_public: bool, + /// Public name of the server if opted into public directory listing. + #[serde(skip_serializing_if = "Option::is_none")] + pub public_name: Option, +} + +/// The response payload received from the telemetry backend. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct HeartbeatResponse { + /// Response status ("ok", "throttled", etc.). + pub status: String, + /// Status or error message from the backend. + #[serde(default)] + pub message: Option, + /// Confirmed server ID acknowledged by the backend. + #[serde(default)] + pub server_id: Option, + /// Recommended seconds to wait before the next heartbeat. + #[serde(default)] + pub next_ping_seconds: Option, +} + +/// Loads an existing server UUID from the given path or generates and persists a new one. +pub fn get_or_create_server_id(path: &Path) -> Uuid { + if path.exists() + && let Ok(content) = std::fs::read_to_string(path) + { + let trimmed = content.trim(); + if let Ok(id) = Uuid::parse_str(trimmed) { + return id; + } + tracing::warn!( + "Failed to parse server ID from {}, generating a new one", + path.display() + ); + } + + let id = Uuid::new_v4(); + if let Err(err) = std::fs::write(path, id.to_string()) { + tracing::warn!("Failed to save server ID to {}: {err}", path.display()); + } + id +} + +/// Resolves the server ID by checking default locations or generating a new one. +#[must_use] +pub fn resolve_server_id() -> Uuid { + let root_path = Path::new(SERVER_ID_FILENAME); + if root_path.exists() { + return get_or_create_server_id(root_path); + } + let data_path = Path::new("data").join(SERVER_ID_FILENAME); + if data_path.exists() { + return get_or_create_server_id(&data_path); + } + get_or_create_server_id(root_path) +} + +/// Builds the telemetry heartbeat payload from current server and runtime state. +#[must_use] +pub fn build_heartbeat_payload( + server: &Server, + server_id: Uuid, + telemetry_config: &TelemetryConfig, +) -> HeartbeatPayload { + let os = sysinfo::System::long_os_version().unwrap_or_else(|| { + let name = sysinfo::System::name().unwrap_or_else(|| std::env::consts::OS.to_string()); + sysinfo::System::os_version().map_or(name.clone(), |ver| format!("{name} {ver}")) + }); + let arch = std::env::consts::ARCH.to_string(); + let cpu_cores = std::thread::available_parallelism().map_or(1, std::num::NonZeroUsize::get); + + let (ram_allocated_mb, total_ram_mb, cpu_model) = if sysinfo::IS_SUPPORTED_SYSTEM { + let mut sys = sysinfo::System::new_all(); + sys.refresh_all(); + let ram_allocated = sys.used_memory() / (1024 * 1024); + let total_ram = sys.total_memory() / (1024 * 1024); + let cpu_model = sys.cpus().first().map_or_else( + || "Unknown".to_string(), + |c| { + let brand = c.brand().trim(); + if brand.is_empty() { + "Unknown".to_string() + } else { + brand.to_string() + } + }, + ); + (ram_allocated, total_ram, cpu_model) + } else { + (0, 0, "Unknown".to_string()) + }; + + let plugins = server + .plugin_manager + .active_plugins() + .into_iter() + .map(|p| PluginInfo { + name: p.name, + version: p.version, + }) + .collect(); + + let is_public = telemetry_config.public; + let public_name = if is_public { + telemetry_config.server_name.clone() + } else { + None + }; + + HeartbeatPayload { + server_id, + server_type: "pumpkin".to_string(), + server_version: env!("CARGO_PKG_VERSION").to_string(), + minecraft_version: pumpkin_data::packet::CURRENT_MC_VERSION.to_string(), + protocol_version: pumpkin_data::packet::CURRENT_MC_VERSION.protocol_version(), + online_players: server.get_player_count(), + max_players: server.advanced_config.networking.java.max_players, + os, + arch, + cpu_cores, + cpu_model, + ram_allocated_mb, + total_ram_mb, + plugins, + is_public, + public_name, + } +} + +/// Starts the background telemetry reporting task using the server's telemetry configuration. +pub fn start_telemetry(server: Arc) { + let config = server.telemetry_config.clone(); + start_telemetry_with_config(server, &config); +} + +/// Starts the background telemetry reporting task with a specific configuration. +pub fn start_telemetry_with_config(server: Arc, config: &TelemetryConfig) { + if !config.enabled { + tracing::trace!("Telemetry is disabled in configuration."); + return; + } + + tracing::info!( + "Anonymous server telemetry is enabled. Sending periodic heartbeats to {}", + config.endpoint + ); + + let user_agent = format!("Pumpkin-Server/{}", env!("CARGO_PKG_VERSION")); + let client = match reqwest::Client::builder() + .timeout(Duration::from_secs(10)) + .user_agent(&user_agent) + .build() + { + Ok(c) => c, + Err(err) => { + tracing::debug!("Failed to create telemetry HTTP client: {err}"); + return; + } + }; + + let server_id = resolve_server_id(); + let interval_secs = config.interval_secs.max(60); + let endpoint = config.endpoint.clone(); + let config = config.clone(); + + tokio::spawn(async move { + let mut interval = tokio::time::interval(Duration::from_secs(interval_secs)); + while !crate::SHOULD_STOP.load(Ordering::Relaxed) { + interval.tick().await; + if crate::SHOULD_STOP.load(Ordering::Relaxed) { + break; + } + + let payload = build_heartbeat_payload(&server, server_id, &config); + + let res = client + .post(&endpoint) + .header(reqwest::header::CONTENT_TYPE, "application/json") + .header(reqwest::header::USER_AGENT, &user_agent) + .json(&payload) + .send() + .await; + + match res { + Ok(resp) => { + if resp.status().is_success() { + tracing::trace!("Telemetry heartbeat successfully transmitted"); + } else { + tracing::debug!( + "Telemetry heartbeat received non-success HTTP status: {}", + resp.status() + ); + } + } + Err(err) => { + tracing::debug!("Telemetry heartbeat failed to send: {err}"); + } + } + } + }); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn server_id_generation_and_persistence() { + let dir = tempfile::tempdir().unwrap(); + let id_path = dir.path().join(".pumpkin-server-id"); + + assert!(!id_path.exists()); + let id1 = get_or_create_server_id(&id_path); + assert!(id_path.exists()); + + let id2 = get_or_create_server_id(&id_path); + assert_eq!(id1, id2); + + // Corrupted file test + std::fs::write(&id_path, "not-a-uuid").unwrap(); + let id3 = get_or_create_server_id(&id_path); + assert_ne!(id1, id3); + } + + #[test] + fn heartbeat_payload_serialization() { + let payload = HeartbeatPayload { + server_id: Uuid::parse_str("a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d").unwrap(), + server_type: "pumpkin".to_string(), + server_version: "0.3.2".to_string(), + minecraft_version: "1.21.4".to_string(), + protocol_version: 769, + online_players: 42, + max_players: 100, + os: "Ubuntu 24.04 LTS".to_string(), + arch: "x86_64".to_string(), + cpu_cores: 8, + cpu_model: "AMD Ryzen 7 5800X 8-Core Processor".to_string(), + ram_allocated_mb: 8192, + total_ram_mb: 16384, + plugins: vec![ + PluginInfo { + name: "Essential Admin Tools".to_string(), + version: "1.0.0".to_string(), + }, + PluginInfo { + name: "PumpkinAuth".to_string(), + version: "1.2.0".to_string(), + }, + ], + is_public: true, + public_name: Some("My High-Performance Pumpkin SMP".to_string()), + }; + + let json_str = serde_json::to_string_pretty(&payload).unwrap(); + let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap(); + + assert_eq!(parsed["server_id"], "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"); + assert_eq!(parsed["server_type"], "pumpkin"); + assert_eq!(parsed["server_version"], "0.3.2"); + assert_eq!(parsed["minecraft_version"], "1.21.4"); + assert_eq!(parsed["protocol_version"], 769); + assert_eq!(parsed["online_players"], 42); + assert_eq!(parsed["max_players"], 100); + assert_eq!(parsed["os"], "Ubuntu 24.04 LTS"); + assert_eq!(parsed["arch"], "x86_64"); + assert_eq!(parsed["cpu_cores"], 8); + assert_eq!(parsed["cpu_model"], "AMD Ryzen 7 5800X 8-Core Processor"); + assert_eq!(parsed["ram_allocated_mb"], 8192); + assert_eq!(parsed["total_ram_mb"], 16384); + assert_eq!(parsed["is_public"], true); + assert_eq!(parsed["public_name"], "My High-Performance Pumpkin SMP"); + assert_eq!(parsed["plugins"].as_array().unwrap().len(), 2); + } + + #[test] + fn heartbeat_response_deserialization() { + let json_data = r#"{ + "status": "ok", + "message": "Heartbeat accepted", + "server_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", + "next_ping_seconds": 300 + }"#; + + let resp: HeartbeatResponse = serde_json::from_str(json_data).unwrap(); + assert_eq!(resp.status, "ok"); + assert_eq!(resp.message.as_deref(), Some("Heartbeat accepted")); + assert_eq!( + resp.server_id.as_deref(), + Some("a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d") + ); + assert_eq!(resp.next_ping_seconds, Some(300)); + } +}