Compare commits
	
		
			5 Commits
		
	
	
		
			67b5c2df07
			...
			07116eb8a6
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 07116eb8a6 | |||
| 3f34f868eb | |||
| bc6f7336d2 | |||
| 01da8631da | |||
| 19cb7f73bc | 
							
								
								
									
										412
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										412
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -2,6 +2,189 @@ | |||||||
| # It is not intended for manual editing. | # It is not intended for manual editing. | ||||||
| version = 4 | version = 4 | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "actix-codec" | ||||||
|  | version = "0.5.2" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" | ||||||
|  | dependencies = [ | ||||||
|  |  "bitflags 2.9.1", | ||||||
|  |  "bytes", | ||||||
|  |  "futures-core", | ||||||
|  |  "futures-sink", | ||||||
|  |  "memchr", | ||||||
|  |  "pin-project-lite", | ||||||
|  |  "tokio", | ||||||
|  |  "tokio-util", | ||||||
|  |  "tracing", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "actix-http" | ||||||
|  | version = "3.11.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "44dfe5c9e0004c623edc65391dfd51daa201e7e30ebd9c9bedf873048ec32bc2" | ||||||
|  | dependencies = [ | ||||||
|  |  "actix-codec", | ||||||
|  |  "actix-rt", | ||||||
|  |  "actix-service", | ||||||
|  |  "actix-utils", | ||||||
|  |  "base64 0.22.1", | ||||||
|  |  "bitflags 2.9.1", | ||||||
|  |  "brotli", | ||||||
|  |  "bytes", | ||||||
|  |  "bytestring", | ||||||
|  |  "derive_more", | ||||||
|  |  "encoding_rs", | ||||||
|  |  "flate2", | ||||||
|  |  "foldhash", | ||||||
|  |  "futures-core", | ||||||
|  |  "h2 0.3.26", | ||||||
|  |  "http 0.2.12", | ||||||
|  |  "httparse", | ||||||
|  |  "httpdate", | ||||||
|  |  "itoa", | ||||||
|  |  "language-tags", | ||||||
|  |  "local-channel", | ||||||
|  |  "mime", | ||||||
|  |  "percent-encoding", | ||||||
|  |  "pin-project-lite", | ||||||
|  |  "rand 0.9.1", | ||||||
|  |  "sha1", | ||||||
|  |  "smallvec", | ||||||
|  |  "tokio", | ||||||
|  |  "tokio-util", | ||||||
|  |  "tracing", | ||||||
|  |  "zstd", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "actix-macros" | ||||||
|  | version = "0.2.4" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" | ||||||
|  | dependencies = [ | ||||||
|  |  "quote", | ||||||
|  |  "syn", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "actix-router" | ||||||
|  | version = "0.5.3" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" | ||||||
|  | dependencies = [ | ||||||
|  |  "bytestring", | ||||||
|  |  "cfg-if", | ||||||
|  |  "http 0.2.12", | ||||||
|  |  "regex", | ||||||
|  |  "regex-lite", | ||||||
|  |  "serde", | ||||||
|  |  "tracing", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "actix-rt" | ||||||
|  | version = "2.10.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "24eda4e2a6e042aa4e55ac438a2ae052d3b5da0ecf83d7411e1a368946925208" | ||||||
|  | dependencies = [ | ||||||
|  |  "futures-core", | ||||||
|  |  "tokio", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "actix-server" | ||||||
|  | version = "2.6.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "a65064ea4a457eaf07f2fba30b4c695bf43b721790e9530d26cb6f9019ff7502" | ||||||
|  | dependencies = [ | ||||||
|  |  "actix-rt", | ||||||
|  |  "actix-service", | ||||||
|  |  "actix-utils", | ||||||
|  |  "futures-core", | ||||||
|  |  "futures-util", | ||||||
|  |  "mio 1.0.4", | ||||||
|  |  "socket2", | ||||||
|  |  "tokio", | ||||||
|  |  "tracing", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "actix-service" | ||||||
|  | version = "2.0.3" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "9e46f36bf0e5af44bdc4bdb36fbbd421aa98c79a9bce724e1edeb3894e10dc7f" | ||||||
|  | dependencies = [ | ||||||
|  |  "futures-core", | ||||||
|  |  "pin-project-lite", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "actix-utils" | ||||||
|  | version = "3.0.1" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" | ||||||
|  | dependencies = [ | ||||||
|  |  "local-waker", | ||||||
|  |  "pin-project-lite", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "actix-web" | ||||||
|  | version = "4.11.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "a597b77b5c6d6a1e1097fddde329a83665e25c5437c696a3a9a4aa514a614dea" | ||||||
|  | dependencies = [ | ||||||
|  |  "actix-codec", | ||||||
|  |  "actix-http", | ||||||
|  |  "actix-macros", | ||||||
|  |  "actix-router", | ||||||
|  |  "actix-rt", | ||||||
|  |  "actix-server", | ||||||
|  |  "actix-service", | ||||||
|  |  "actix-utils", | ||||||
|  |  "actix-web-codegen", | ||||||
|  |  "bytes", | ||||||
|  |  "bytestring", | ||||||
|  |  "cfg-if", | ||||||
|  |  "cookie", | ||||||
|  |  "derive_more", | ||||||
|  |  "encoding_rs", | ||||||
|  |  "foldhash", | ||||||
|  |  "futures-core", | ||||||
|  |  "futures-util", | ||||||
|  |  "impl-more", | ||||||
|  |  "itoa", | ||||||
|  |  "language-tags", | ||||||
|  |  "log", | ||||||
|  |  "mime", | ||||||
|  |  "once_cell", | ||||||
|  |  "pin-project-lite", | ||||||
|  |  "regex", | ||||||
|  |  "regex-lite", | ||||||
|  |  "serde", | ||||||
|  |  "serde_json", | ||||||
|  |  "serde_urlencoded", | ||||||
|  |  "smallvec", | ||||||
|  |  "socket2", | ||||||
|  |  "time", | ||||||
|  |  "tracing", | ||||||
|  |  "url", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "actix-web-codegen" | ||||||
|  | version = "4.3.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" | ||||||
|  | dependencies = [ | ||||||
|  |  "actix-router", | ||||||
|  |  "proc-macro2", | ||||||
|  |  "quote", | ||||||
|  |  "syn", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "addr2line" | name = "addr2line" | ||||||
| version = "0.24.2" | version = "0.24.2" | ||||||
| @ -75,6 +258,21 @@ dependencies = [ | |||||||
|  "memchr", |  "memchr", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "alloc-no-stdlib" | ||||||
|  | version = "2.0.4" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "alloc-stdlib" | ||||||
|  | version = "0.2.2" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" | ||||||
|  | dependencies = [ | ||||||
|  |  "alloc-no-stdlib", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "allocator-api2" | name = "allocator-api2" | ||||||
| version = "0.2.21" | version = "0.2.21" | ||||||
| @ -398,6 +596,27 @@ dependencies = [ | |||||||
|  "serde_with", |  "serde_with", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "brotli" | ||||||
|  | version = "8.0.2" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" | ||||||
|  | dependencies = [ | ||||||
|  |  "alloc-no-stdlib", | ||||||
|  |  "alloc-stdlib", | ||||||
|  |  "brotli-decompressor", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "brotli-decompressor" | ||||||
|  | version = "5.0.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" | ||||||
|  | dependencies = [ | ||||||
|  |  "alloc-no-stdlib", | ||||||
|  |  "alloc-stdlib", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "bstr" | name = "bstr" | ||||||
| version = "1.12.0" | version = "1.12.0" | ||||||
| @ -427,6 +646,15 @@ version = "1.10.1" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" | checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "bytestring" | ||||||
|  | version = "1.4.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "e465647ae23b2823b0753f50decb2d5a86d2bb2cac04788fafd1f80e45378e5f" | ||||||
|  | dependencies = [ | ||||||
|  |  "bytes", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "camino" | name = "camino" | ||||||
| version = "1.1.10" | version = "1.1.10" | ||||||
| @ -506,6 +734,8 @@ version = "1.2.27" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" | checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  |  "jobserver", | ||||||
|  |  "libc", | ||||||
|  "shlex", |  "shlex", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| @ -710,6 +940,17 @@ dependencies = [ | |||||||
|  "unicode-segmentation", |  "unicode-segmentation", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "cookie" | ||||||
|  | version = "0.16.2" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" | ||||||
|  | dependencies = [ | ||||||
|  |  "percent-encoding", | ||||||
|  |  "time", | ||||||
|  |  "version_check", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "core-foundation" | name = "core-foundation" | ||||||
| version = "0.9.4" | version = "0.9.4" | ||||||
| @ -763,6 +1004,25 @@ dependencies = [ | |||||||
|  "crossbeam-utils", |  "crossbeam-utils", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "crossbeam-deque" | ||||||
|  | version = "0.8.6" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" | ||||||
|  | dependencies = [ | ||||||
|  |  "crossbeam-epoch", | ||||||
|  |  "crossbeam-utils", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "crossbeam-epoch" | ||||||
|  | version = "0.9.18" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" | ||||||
|  | dependencies = [ | ||||||
|  |  "crossbeam-utils", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "crossbeam-utils" | name = "crossbeam-utils" | ||||||
| version = "0.8.21" | version = "0.8.21" | ||||||
| @ -973,6 +1233,7 @@ dependencies = [ | |||||||
|  "proc-macro2", |  "proc-macro2", | ||||||
|  "quote", |  "quote", | ||||||
|  "syn", |  "syn", | ||||||
|  |  "unicode-xid", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| @ -1907,6 +2168,19 @@ dependencies = [ | |||||||
|  "tokio", |  "tokio", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "harmony_inventory_agent" | ||||||
|  | version = "0.1.0" | ||||||
|  | dependencies = [ | ||||||
|  |  "actix-web", | ||||||
|  |  "env_logger", | ||||||
|  |  "log", | ||||||
|  |  "serde", | ||||||
|  |  "serde_json", | ||||||
|  |  "sysinfo", | ||||||
|  |  "uuid", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "harmony_macros" | name = "harmony_macros" | ||||||
| version = "0.1.0" | version = "0.1.0" | ||||||
| @ -2346,7 +2620,7 @@ dependencies = [ | |||||||
|  "js-sys", |  "js-sys", | ||||||
|  "log", |  "log", | ||||||
|  "wasm-bindgen", |  "wasm-bindgen", | ||||||
|  "windows-core", |  "windows-core 0.61.2", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| @ -2471,6 +2745,12 @@ dependencies = [ | |||||||
|  "icu_properties", |  "icu_properties", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "impl-more" | ||||||
|  | version = "0.1.9" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "indenter" | name = "indenter" | ||||||
| version = "0.3.3" | version = "0.3.3" | ||||||
| @ -2655,6 +2935,16 @@ dependencies = [ | |||||||
|  "syn", |  "syn", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "jobserver" | ||||||
|  | version = "0.1.33" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" | ||||||
|  | dependencies = [ | ||||||
|  |  "getrandom 0.3.3", | ||||||
|  |  "libc", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "js-sys" | name = "js-sys" | ||||||
| version = "0.3.77" | version = "0.3.77" | ||||||
| @ -2857,6 +3147,12 @@ dependencies = [ | |||||||
|  "tracing", |  "tracing", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "language-tags" | ||||||
|  | version = "0.3.2" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "lazy_static" | name = "lazy_static" | ||||||
| version = "1.5.0" | version = "1.5.0" | ||||||
| @ -2920,6 +3216,23 @@ version = "0.8.0" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" | checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "local-channel" | ||||||
|  | version = "0.1.5" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" | ||||||
|  | dependencies = [ | ||||||
|  |  "futures-core", | ||||||
|  |  "futures-sink", | ||||||
|  |  "local-waker", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "local-waker" | ||||||
|  | version = "0.1.4" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "lock_api" | name = "lock_api" | ||||||
| version = "0.4.13" | version = "0.4.13" | ||||||
| @ -3055,6 +3368,15 @@ dependencies = [ | |||||||
|  "serde", |  "serde", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "ntapi" | ||||||
|  | version = "0.4.1" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" | ||||||
|  | dependencies = [ | ||||||
|  |  "winapi", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "num-bigint" | name = "num-bigint" | ||||||
| version = "0.4.6" | version = "0.4.6" | ||||||
| @ -3839,6 +4161,26 @@ dependencies = [ | |||||||
|  "unicode-width 0.2.0", |  "unicode-width 0.2.0", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "rayon" | ||||||
|  | version = "1.11.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" | ||||||
|  | dependencies = [ | ||||||
|  |  "either", | ||||||
|  |  "rayon-core", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "rayon-core" | ||||||
|  | version = "1.13.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" | ||||||
|  | dependencies = [ | ||||||
|  |  "crossbeam-deque", | ||||||
|  |  "crossbeam-utils", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "redox_syscall" | name = "redox_syscall" | ||||||
| version = "0.5.13" | version = "0.5.13" | ||||||
| @ -3902,6 +4244,12 @@ dependencies = [ | |||||||
|  "regex-syntax", |  "regex-syntax", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "regex-lite" | ||||||
|  | version = "0.1.6" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "regex-syntax" | name = "regex-syntax" | ||||||
| version = "0.8.5" | version = "0.8.5" | ||||||
| @ -4956,6 +5304,21 @@ dependencies = [ | |||||||
|  "syn", |  "syn", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "sysinfo" | ||||||
|  | version = "0.30.13" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "0a5b4ddaee55fb2bea2bf0e5000747e5f5c0de765e5a5ff87f4cd106439f4bb3" | ||||||
|  | dependencies = [ | ||||||
|  |  "cfg-if", | ||||||
|  |  "core-foundation-sys", | ||||||
|  |  "libc", | ||||||
|  |  "ntapi", | ||||||
|  |  "once_cell", | ||||||
|  |  "rayon", | ||||||
|  |  "windows", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "system-configuration" | name = "system-configuration" | ||||||
| version = "0.5.1" | version = "0.5.1" | ||||||
| @ -5759,6 +6122,25 @@ version = "0.4.0" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "windows" | ||||||
|  | version = "0.52.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" | ||||||
|  | dependencies = [ | ||||||
|  |  "windows-core 0.52.0", | ||||||
|  |  "windows-targets 0.52.6", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "windows-core" | ||||||
|  | version = "0.52.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" | ||||||
|  | dependencies = [ | ||||||
|  |  "windows-targets 0.52.6", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "windows-core" | name = "windows-core" | ||||||
| version = "0.61.2" | version = "0.61.2" | ||||||
| @ -6242,3 +6624,31 @@ dependencies = [ | |||||||
|  "quote", |  "quote", | ||||||
|  "syn", |  "syn", | ||||||
| ] | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "zstd" | ||||||
|  | version = "0.13.3" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" | ||||||
|  | dependencies = [ | ||||||
|  |  "zstd-safe", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "zstd-safe" | ||||||
|  | version = "7.2.4" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" | ||||||
|  | dependencies = [ | ||||||
|  |  "zstd-sys", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "zstd-sys" | ||||||
|  | version = "2.0.15+zstd.1.5.7" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" | ||||||
|  | dependencies = [ | ||||||
|  |  "cc", | ||||||
|  |  "pkg-config", | ||||||
|  | ] | ||||||
|  | |||||||
| @ -12,6 +12,7 @@ members = [ | |||||||
|   "harmony_cli", |   "harmony_cli", | ||||||
|   "k3d", |   "k3d", | ||||||
|   "harmony_composer", |   "harmony_composer", | ||||||
|  |   "harmony_inventory_agent", | ||||||
|   "harmony_secret_derive", |   "harmony_secret_derive", | ||||||
|   "harmony_secret", |   "harmony_secret", | ||||||
| ] | ] | ||||||
| @ -62,3 +63,5 @@ tar = "0.4.44" | |||||||
| lazy_static = "1.5.0" | lazy_static = "1.5.0" | ||||||
| directories = "6.0.0" | directories = "6.0.0" | ||||||
| thiserror = "2.0.14" | thiserror = "2.0.14" | ||||||
|  | serde = { version = "1.0.209", features = ["derive", "rc"] } | ||||||
|  | serde_json = "1.0.127" | ||||||
|  | |||||||
| @ -16,8 +16,8 @@ reqwest = { version = "0.11", features = ["blocking", "json"] } | |||||||
| russh = "0.45.0" | russh = "0.45.0" | ||||||
| rust-ipmi = "0.1.1" | rust-ipmi = "0.1.1" | ||||||
| semver = "1.0.23" | semver = "1.0.23" | ||||||
| serde = { version = "1.0.209", features = ["derive", "rc"] } | serde.workspace = true | ||||||
| serde_json = "1.0.127" | serde_json.workspace = true | ||||||
| tokio.workspace = true | tokio.workspace = true | ||||||
| derive-new.workspace = true | derive-new.workspace = true | ||||||
| log.workspace = true | log.workspace = true | ||||||
|  | |||||||
							
								
								
									
										13
									
								
								harmony_inventory_agent/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								harmony_inventory_agent/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | [package] | ||||||
|  | name = "harmony_inventory_agent" | ||||||
|  | version = "0.1.0" | ||||||
|  | edition = "2024" | ||||||
|  | 
 | ||||||
|  | [dependencies] | ||||||
|  | actix-web = "4.4" | ||||||
|  | sysinfo = "0.30" | ||||||
|  | serde.workspace = true | ||||||
|  | serde_json.workspace = true | ||||||
|  | log.workspace = true | ||||||
|  | env_logger.workspace = true | ||||||
|  | uuid.workspace = true | ||||||
							
								
								
									
										571
									
								
								harmony_inventory_agent/src/hwinfo.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										571
									
								
								harmony_inventory_agent/src/hwinfo.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,571 @@ | |||||||
|  | use serde::{Deserialize, Serialize}; | ||||||
|  | use serde_json::Value; | ||||||
|  | use std::fs; | ||||||
|  | use std::path::Path; | ||||||
|  | use std::process::Command; | ||||||
|  | use sysinfo::System; | ||||||
|  | 
 | ||||||
|  | #[derive(Serialize, Deserialize, Debug)] | ||||||
|  | pub struct PhysicalHost { | ||||||
|  |     pub storage_drives: Vec<StorageDrive>, | ||||||
|  |     pub storage_controller: StorageController, | ||||||
|  |     pub memory_modules: Vec<MemoryModule>, | ||||||
|  |     pub cpus: Vec<CPU>, | ||||||
|  |     pub chipset: Chipset, | ||||||
|  |     pub network_interfaces: Vec<NetworkInterface>, | ||||||
|  |     pub management_interface: Option<ManagementInterface>, | ||||||
|  |     pub host_uuid: String, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Serialize, Deserialize, Debug)] | ||||||
|  | pub struct StorageDrive { | ||||||
|  |     pub name: String, | ||||||
|  |     pub model: String, | ||||||
|  |     pub serial: String, | ||||||
|  |     pub size_bytes: u64, | ||||||
|  |     pub logical_block_size: u32, | ||||||
|  |     pub physical_block_size: u32, | ||||||
|  |     pub rotational: bool, | ||||||
|  |     pub wwn: Option<String>, | ||||||
|  |     pub interface_type: String, | ||||||
|  |     pub smart_status: Option<String>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Serialize, Deserialize, Debug)] | ||||||
|  | pub struct StorageController { | ||||||
|  |     pub name: String, | ||||||
|  |     pub driver: String, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Serialize, Deserialize, Debug)] | ||||||
|  | pub struct MemoryModule { | ||||||
|  |     pub size_bytes: u64, | ||||||
|  |     pub speed_mhz: Option<u32>, | ||||||
|  |     pub manufacturer: Option<String>, | ||||||
|  |     pub part_number: Option<String>, | ||||||
|  |     pub serial_number: Option<String>, | ||||||
|  |     pub rank: Option<u8>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Serialize, Deserialize, Debug)] | ||||||
|  | pub struct CPU { | ||||||
|  |     pub model: String, | ||||||
|  |     pub vendor: String, | ||||||
|  |     pub cores: u32, | ||||||
|  |     pub threads: u32, | ||||||
|  |     pub frequency_mhz: u64, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Serialize, Deserialize, Debug)] | ||||||
|  | pub struct Chipset { | ||||||
|  |     pub name: String, | ||||||
|  |     pub vendor: String, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Serialize, Deserialize, Debug)] | ||||||
|  | pub struct NetworkInterface { | ||||||
|  |     pub name: String, | ||||||
|  |     pub mac_address: String, | ||||||
|  |     pub speed_mbps: Option<u32>, | ||||||
|  |     pub is_up: bool, | ||||||
|  |     pub mtu: u32, | ||||||
|  |     pub ipv4_addresses: Vec<String>, | ||||||
|  |     pub ipv6_addresses: Vec<String>, | ||||||
|  |     pub driver: String, | ||||||
|  |     pub firmware_version: Option<String>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Serialize, Deserialize, Debug)] | ||||||
|  | pub struct ManagementInterface { | ||||||
|  |     pub kind: String, | ||||||
|  |     pub address: Option<String>, | ||||||
|  |     pub firmware: Option<String>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl PhysicalHost { | ||||||
|  |     pub fn gather() -> Self { | ||||||
|  |         let mut sys = System::new_all(); | ||||||
|  |         sys.refresh_all(); | ||||||
|  | 
 | ||||||
|  |         Self { | ||||||
|  |             storage_drives: Self::gather_storage_drives(), | ||||||
|  |             storage_controller: Self::gather_storage_controller(), | ||||||
|  |             memory_modules: Self::gather_memory_modules(), | ||||||
|  |             cpus: Self::gather_cpus(&sys), | ||||||
|  |             chipset: Self::gather_chipset(), | ||||||
|  |             network_interfaces: Self::gather_network_interfaces(), | ||||||
|  |             management_interface: Self::gather_management_interface(), | ||||||
|  |             host_uuid: Self::get_host_uuid(), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn gather_storage_drives() -> Vec<StorageDrive> { | ||||||
|  |         let mut drives = Vec::new(); | ||||||
|  | 
 | ||||||
|  |         // Use lsblk with JSON output for robust parsing
 | ||||||
|  |         if let Ok(output) = Command::new("lsblk") | ||||||
|  |             .args([ | ||||||
|  |                 "-d", | ||||||
|  |                 "-o", | ||||||
|  |                 "NAME,MODEL,SERIAL,SIZE,ROTA,WWN", | ||||||
|  |                 "-n", | ||||||
|  |                 "-e", | ||||||
|  |                 "7", | ||||||
|  |                 "--json", | ||||||
|  |             ]) | ||||||
|  |             .output() | ||||||
|  |             && output.status.success() | ||||||
|  |             && let Ok(json) = serde_json::from_slice::<Value>(&output.stdout) | ||||||
|  |             && let Some(blockdevices) = json.get("blockdevices").and_then(|v| v.as_array()) | ||||||
|  |         { | ||||||
|  |             for device in blockdevices { | ||||||
|  |                 let name = device | ||||||
|  |                     .get("name") | ||||||
|  |                     .and_then(|v| v.as_str()) | ||||||
|  |                     .unwrap_or("") | ||||||
|  |                     .to_string(); | ||||||
|  |                 if name.is_empty() { | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 let model = device | ||||||
|  |                     .get("model") | ||||||
|  |                     .and_then(|v| v.as_str()) | ||||||
|  |                     .map(|s| s.trim().to_string()) | ||||||
|  |                     .unwrap_or_default(); | ||||||
|  | 
 | ||||||
|  |                 let serial = device | ||||||
|  |                     .get("serial") | ||||||
|  |                     .and_then(|v| v.as_str()) | ||||||
|  |                     .map(|s| s.trim().to_string()) | ||||||
|  |                     .unwrap_or_default(); | ||||||
|  | 
 | ||||||
|  |                 let size_str = device.get("size").and_then(|v| v.as_str()).unwrap_or("0"); | ||||||
|  |                 let size_bytes = Self::parse_size(size_str).unwrap_or(0); | ||||||
|  | 
 | ||||||
|  |                 let rotational = device | ||||||
|  |                     .get("rota") | ||||||
|  |                     .and_then(|v| v.as_bool()) | ||||||
|  |                     .unwrap_or(false); | ||||||
|  | 
 | ||||||
|  |                 let wwn = device | ||||||
|  |                     .get("wwn") | ||||||
|  |                     .and_then(|v| v.as_str()) | ||||||
|  |                     .map(|s| s.trim().to_string()) | ||||||
|  |                     .filter(|s| !s.is_empty() && s != "null"); | ||||||
|  | 
 | ||||||
|  |                 let device_path = Path::new("/sys/block").join(&name); | ||||||
|  | 
 | ||||||
|  |                 let mut drive = StorageDrive { | ||||||
|  |                     name: name.clone(), | ||||||
|  |                     model, | ||||||
|  |                     serial, | ||||||
|  |                     size_bytes, | ||||||
|  |                     logical_block_size: Self::read_sysfs_u32( | ||||||
|  |                         &device_path.join("queue/logical_block_size"), | ||||||
|  |                     ) | ||||||
|  |                     .unwrap_or(512), | ||||||
|  |                     physical_block_size: Self::read_sysfs_u32( | ||||||
|  |                         &device_path.join("queue/physical_block_size"), | ||||||
|  |                     ) | ||||||
|  |                     .unwrap_or(512), | ||||||
|  |                     rotational, | ||||||
|  |                     wwn, | ||||||
|  |                     interface_type: Self::get_interface_type(&name, &device_path), | ||||||
|  |                     smart_status: Self::get_smart_status(&name), | ||||||
|  |                 }; | ||||||
|  | 
 | ||||||
|  |                 // Enhance with additional sysfs info if available
 | ||||||
|  |                 if device_path.exists() { | ||||||
|  |                     if drive.model.is_empty() { | ||||||
|  |                         drive.model = Self::read_sysfs_string(&device_path.join("device/model")); | ||||||
|  |                     } | ||||||
|  |                     if drive.serial.is_empty() { | ||||||
|  |                         drive.serial = Self::read_sysfs_string(&device_path.join("device/serial")); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 drives.push(drive); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         drives | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn gather_storage_controller() -> StorageController { | ||||||
|  |         let mut controller = StorageController { | ||||||
|  |             name: "Unknown".to_string(), | ||||||
|  |             driver: "Unknown".to_string(), | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         // Use lspci with JSON output if available
 | ||||||
|  |         if let Ok(output) = Command::new("lspci") | ||||||
|  |             .args(["-nn", "-d", "::0100", "-J"]) // Storage controllers class with JSON
 | ||||||
|  |             .output() | ||||||
|  |             && output.status.success() | ||||||
|  |             && let Ok(json) = serde_json::from_slice::<Value>(&output.stdout) | ||||||
|  |             && let Some(devices) = json.as_array() | ||||||
|  |         { | ||||||
|  |             for device in devices { | ||||||
|  |                 if let Some(device_info) = device.as_object() | ||||||
|  |                     && let Some(name) = device_info | ||||||
|  |                         .get("device") | ||||||
|  |                         .and_then(|v| v.as_object()) | ||||||
|  |                         .and_then(|v| v.get("name")) | ||||||
|  |                         .and_then(|v| v.as_str()) | ||||||
|  |                 { | ||||||
|  |                     controller.name = name.to_string(); | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Fallback to text output if JSON fails
 | ||||||
|  |         if controller.name == "Unknown" | ||||||
|  |             && let Ok(output) = Command::new("lspci") | ||||||
|  |                 .args(["-nn", "-d", "::0100"]) // Storage controllers class
 | ||||||
|  |                 .output() | ||||||
|  |             && output.status.success() | ||||||
|  |         { | ||||||
|  |             let output_str = String::from_utf8_lossy(&output.stdout); | ||||||
|  |             if let Some(line) = output_str.lines().next() { | ||||||
|  |                 let parts: Vec<&str> = line.split(':').collect(); | ||||||
|  |                 if parts.len() > 2 { | ||||||
|  |                     controller.name = parts[2].trim().to_string(); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Try to get driver info from lsmod
 | ||||||
|  |         if let Ok(output) = Command::new("lsmod").output() | ||||||
|  |             && output.status.success() | ||||||
|  |         { | ||||||
|  |             let output_str = String::from_utf8_lossy(&output.stdout); | ||||||
|  |             for line in output_str.lines() { | ||||||
|  |                 if line.contains("ahci") | ||||||
|  |                     || line.contains("nvme") | ||||||
|  |                     || line.contains("megaraid") | ||||||
|  |                     || line.contains("mpt3sas") | ||||||
|  |                 { | ||||||
|  |                     let parts: Vec<&str> = line.split_whitespace().collect(); | ||||||
|  |                     if !parts.is_empty() { | ||||||
|  |                         controller.driver = parts[0].to_string(); | ||||||
|  |                         break; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         controller | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn gather_memory_modules() -> Vec<MemoryModule> { | ||||||
|  |         let mut modules = Vec::new(); | ||||||
|  | 
 | ||||||
|  |         if let Ok(output) = Command::new("dmidecode").arg("--type").arg("17").output() | ||||||
|  |             && output.status.success() | ||||||
|  |         { | ||||||
|  |             let output_str = String::from_utf8_lossy(&output.stdout); | ||||||
|  |             let sections: Vec<&str> = output_str.split("Memory Device").collect(); | ||||||
|  | 
 | ||||||
|  |             for section in sections.into_iter().skip(1) { | ||||||
|  |                 let mut module = MemoryModule { | ||||||
|  |                     size_bytes: 0, | ||||||
|  |                     speed_mhz: None, | ||||||
|  |                     manufacturer: None, | ||||||
|  |                     part_number: None, | ||||||
|  |                     serial_number: None, | ||||||
|  |                     rank: None, | ||||||
|  |                 }; | ||||||
|  | 
 | ||||||
|  |                 for line in section.lines() { | ||||||
|  |                     let line = line.trim(); | ||||||
|  |                     if let Some(size_str) = line.strip_prefix("Size: ") { | ||||||
|  |                         if size_str != "No Module Installed" | ||||||
|  |                             && let Some((num, unit)) = size_str.split_once(' ') | ||||||
|  |                             && let Ok(num) = num.parse::<u64>() | ||||||
|  |                         { | ||||||
|  |                             module.size_bytes = match unit { | ||||||
|  |                                 "MB" => num * 1024 * 1024, | ||||||
|  |                                 "GB" => num * 1024 * 1024 * 1024, | ||||||
|  |                                 "KB" => num * 1024, | ||||||
|  |                                 _ => 0, | ||||||
|  |                             }; | ||||||
|  |                         } | ||||||
|  |                     } else if let Some(speed_str) = line.strip_prefix("Speed: ") { | ||||||
|  |                         if let Some((num, _unit)) = speed_str.split_once(' ') { | ||||||
|  |                             module.speed_mhz = num.parse().ok(); | ||||||
|  |                         } | ||||||
|  |                     } else if let Some(man) = line.strip_prefix("Manufacturer: ") { | ||||||
|  |                         module.manufacturer = Some(man.to_string()); | ||||||
|  |                     } else if let Some(part) = line.strip_prefix("Part Number: ") { | ||||||
|  |                         module.part_number = Some(part.to_string()); | ||||||
|  |                     } else if let Some(serial) = line.strip_prefix("Serial Number: ") { | ||||||
|  |                         module.serial_number = Some(serial.to_string()); | ||||||
|  |                     } else if let Some(rank) = line.strip_prefix("Rank: ") { | ||||||
|  |                         module.rank = rank.parse().ok(); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 if module.size_bytes > 0 { | ||||||
|  |                     modules.push(module); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         modules | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn gather_cpus(sys: &System) -> Vec<CPU> { | ||||||
|  |         let mut cpus = Vec::new(); | ||||||
|  |         let global_cpu = sys.global_cpu_info(); | ||||||
|  | 
 | ||||||
|  |         cpus.push(CPU { | ||||||
|  |             model: global_cpu.brand().to_string(), | ||||||
|  |             vendor: global_cpu.vendor_id().to_string(), | ||||||
|  |             cores: sys.physical_core_count().unwrap_or(1) as u32, | ||||||
|  |             threads: sys.cpus().len() as u32, | ||||||
|  |             frequency_mhz: global_cpu.frequency(), | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         cpus | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn gather_chipset() -> Chipset { | ||||||
|  |         Chipset { | ||||||
|  |             name: Self::read_dmi("board-product-name").unwrap_or_else(|| "Unknown".to_string()), | ||||||
|  |             vendor: Self::read_dmi("board-manufacturer").unwrap_or_else(|| "Unknown".to_string()), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn gather_network_interfaces() -> Vec<NetworkInterface> { | ||||||
|  |         let mut interfaces = Vec::new(); | ||||||
|  |         let sys_net_path = Path::new("/sys/class/net"); | ||||||
|  | 
 | ||||||
|  |         if let Ok(entries) = fs::read_dir(sys_net_path) { | ||||||
|  |             for entry in entries.flatten() { | ||||||
|  |                 let iface_name = entry.file_name().into_string().unwrap_or_default(); | ||||||
|  |                 let iface_path = entry.path(); | ||||||
|  | 
 | ||||||
|  |                 // Skip virtual interfaces
 | ||||||
|  |                 if iface_name.starts_with("lo") | ||||||
|  |                     || iface_name.starts_with("docker") | ||||||
|  |                     || iface_name.starts_with("virbr") | ||||||
|  |                     || iface_name.starts_with("veth") | ||||||
|  |                     || iface_name.starts_with("br-") | ||||||
|  |                     || iface_name.starts_with("tun") | ||||||
|  |                     || iface_name.starts_with("wg") | ||||||
|  |                 { | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 // Check if it's a physical interface by looking for device directory
 | ||||||
|  |                 if !iface_path.join("device").exists() { | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 let mac_address = Self::read_sysfs_string(&iface_path.join("address")); | ||||||
|  |                 let speed_mbps = Self::read_sysfs_u32(&iface_path.join("speed")); | ||||||
|  |                 let operstate = Self::read_sysfs_string(&iface_path.join("operstate")); | ||||||
|  |                 let mtu = Self::read_sysfs_u32(&iface_path.join("mtu")).unwrap_or(1500); | ||||||
|  |                 let driver = Self::read_sysfs_string(&iface_path.join("device/driver/module")); | ||||||
|  |                 let firmware_version = | ||||||
|  |                     Self::read_sysfs_opt_string(&iface_path.join("device/firmware_version")); | ||||||
|  | 
 | ||||||
|  |                 // Get IP addresses using ip command with JSON output
 | ||||||
|  |                 let (ipv4_addresses, ipv6_addresses) = Self::get_interface_ips_json(&iface_name); | ||||||
|  | 
 | ||||||
|  |                 interfaces.push(NetworkInterface { | ||||||
|  |                     name: iface_name, | ||||||
|  |                     mac_address, | ||||||
|  |                     speed_mbps, | ||||||
|  |                     is_up: operstate == "up", | ||||||
|  |                     mtu, | ||||||
|  |                     ipv4_addresses, | ||||||
|  |                     ipv6_addresses, | ||||||
|  |                     driver, | ||||||
|  |                     firmware_version, | ||||||
|  |                 }); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         interfaces | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn gather_management_interface() -> Option<ManagementInterface> { | ||||||
|  |         // Try to detect common management interfaces
 | ||||||
|  |         if Path::new("/dev/ipmi0").exists() { | ||||||
|  |             Some(ManagementInterface { | ||||||
|  |                 kind: "IPMI".to_string(), | ||||||
|  |                 address: None, | ||||||
|  |                 firmware: Self::read_dmi("bios-version"), | ||||||
|  |             }) | ||||||
|  |         } else if Path::new("/sys/class/misc/mei").exists() { | ||||||
|  |             Some(ManagementInterface { | ||||||
|  |                 kind: "Intel ME".to_string(), | ||||||
|  |                 address: None, | ||||||
|  |                 firmware: None, | ||||||
|  |             }) | ||||||
|  |         } else { | ||||||
|  |             None | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn get_host_uuid() -> String { | ||||||
|  |         Self::read_dmi("system-uuid").unwrap() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Helper methods
 | ||||||
|  |     fn read_sysfs_string(path: &Path) -> String { | ||||||
|  |         fs::read_to_string(path) | ||||||
|  |             .unwrap_or_default() | ||||||
|  |             .trim() | ||||||
|  |             .to_string() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn read_sysfs_opt_string(path: &Path) -> Option<String> { | ||||||
|  |         fs::read_to_string(path) | ||||||
|  |             .ok() | ||||||
|  |             .map(|s| s.trim().to_string()) | ||||||
|  |             .filter(|s| !s.is_empty()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn read_sysfs_u32(path: &Path) -> Option<u32> { | ||||||
|  |         fs::read_to_string(path) | ||||||
|  |             .ok() | ||||||
|  |             .and_then(|s| s.trim().parse().ok()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn read_dmi(field: &str) -> Option<String> { | ||||||
|  |         Command::new("dmidecode") | ||||||
|  |             .arg("-s") | ||||||
|  |             .arg(field) | ||||||
|  |             .output() | ||||||
|  |             .ok() | ||||||
|  |             .filter(|output| output.status.success()) | ||||||
|  |             .and_then(|output| String::from_utf8(output.stdout).ok()) | ||||||
|  |             .map(|s| s.trim().to_string()) | ||||||
|  |             .filter(|s| !s.is_empty()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn get_interface_type(device_name: &str, device_path: &Path) -> String { | ||||||
|  |         if device_name.starts_with("nvme") { | ||||||
|  |             "NVMe".to_string() | ||||||
|  |         } else if device_name.starts_with("sd") { | ||||||
|  |             "SATA".to_string() | ||||||
|  |         } else if device_name.starts_with("hd") { | ||||||
|  |             "IDE".to_string() | ||||||
|  |         } else if device_name.starts_with("vd") { | ||||||
|  |             "VirtIO".to_string() | ||||||
|  |         } else { | ||||||
|  |             // Try to determine from device path
 | ||||||
|  |             Self::read_sysfs_string(&device_path.join("device/subsystem")) | ||||||
|  |                 .split('/') | ||||||
|  |                 .next_back() | ||||||
|  |                 .unwrap_or("Unknown") | ||||||
|  |                 .to_string() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn get_smart_status(device_name: &str) -> Option<String> { | ||||||
|  |         Command::new("smartctl") | ||||||
|  |             .arg("-H") | ||||||
|  |             .arg(format!("/dev/{}", device_name)) | ||||||
|  |             .output() | ||||||
|  |             .ok() | ||||||
|  |             .filter(|output| output.status.success()) | ||||||
|  |             .and_then(|output| String::from_utf8(output.stdout).ok()) | ||||||
|  |             .and_then(|s| { | ||||||
|  |                 s.lines() | ||||||
|  |                     .find(|line| line.contains("SMART overall-health self-assessment")) | ||||||
|  |                     .and_then(|line| line.split(':').nth(1)) | ||||||
|  |                     .map(|s| s.trim().to_string()) | ||||||
|  |             }) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn parse_size(size_str: &str) -> Option<u64> { | ||||||
|  |         if size_str.ends_with('T') { | ||||||
|  |             size_str[..size_str.len() - 1] | ||||||
|  |                 .parse::<u64>() | ||||||
|  |                 .ok() | ||||||
|  |                 .map(|t| t * 1024 * 1024 * 1024 * 1024) | ||||||
|  |         } else if size_str.ends_with('G') { | ||||||
|  |             size_str[..size_str.len() - 1] | ||||||
|  |                 .parse::<u64>() | ||||||
|  |                 .ok() | ||||||
|  |                 .map(|g| g * 1024 * 1024 * 1024) | ||||||
|  |         } else if size_str.ends_with('M') { | ||||||
|  |             size_str[..size_str.len() - 1] | ||||||
|  |                 .parse::<u64>() | ||||||
|  |                 .ok() | ||||||
|  |                 .map(|m| m * 1024 * 1024) | ||||||
|  |         } else if size_str.ends_with('K') { | ||||||
|  |             size_str[..size_str.len() - 1] | ||||||
|  |                 .parse::<u64>() | ||||||
|  |                 .ok() | ||||||
|  |                 .map(|k| k * 1024) | ||||||
|  |         } else if size_str.ends_with('B') { | ||||||
|  |             size_str[..size_str.len() - 1].parse::<u64>().ok() | ||||||
|  |         } else { | ||||||
|  |             size_str.parse::<u64>().ok() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn get_interface_ips_json(iface_name: &str) -> (Vec<String>, Vec<String>) { | ||||||
|  |         let mut ipv4 = Vec::new(); | ||||||
|  |         let mut ipv6 = Vec::new(); | ||||||
|  | 
 | ||||||
|  |         // Get IPv4 addresses using JSON output
 | ||||||
|  |         if let Ok(output) = Command::new("ip") | ||||||
|  |             .args(["-j", "-4", "addr", "show", iface_name]) | ||||||
|  |             .output() | ||||||
|  |             && output.status.success() | ||||||
|  |             && let Ok(json) = serde_json::from_slice::<Value>(&output.stdout) | ||||||
|  |             && let Some(addrs) = json.as_array() | ||||||
|  |         { | ||||||
|  |             for addr_info in addrs { | ||||||
|  |                 if let Some(addr_info_obj) = addr_info.as_object() | ||||||
|  |                     && let Some(addr_info) = | ||||||
|  |                         addr_info_obj.get("addr_info").and_then(|v| v.as_array()) | ||||||
|  |                 { | ||||||
|  |                     for addr in addr_info { | ||||||
|  |                         if let Some(addr_obj) = addr.as_object() | ||||||
|  |                             && let Some(ip) = addr_obj.get("local").and_then(|v| v.as_str()) | ||||||
|  |                         { | ||||||
|  |                             ipv4.push(ip.to_string()); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Get IPv6 addresses using JSON output
 | ||||||
|  |         if let Ok(output) = Command::new("ip") | ||||||
|  |             .args(["-j", "-6", "addr", "show", iface_name]) | ||||||
|  |             .output() | ||||||
|  |             && output.status.success() | ||||||
|  |             && let Ok(json) = serde_json::from_slice::<Value>(&output.stdout) | ||||||
|  |             && let Some(addrs) = json.as_array() | ||||||
|  |         { | ||||||
|  |             for addr_info in addrs { | ||||||
|  |                 if let Some(addr_info_obj) = addr_info.as_object() | ||||||
|  |                     && let Some(addr_info) = | ||||||
|  |                         addr_info_obj.get("addr_info").and_then(|v| v.as_array()) | ||||||
|  |                 { | ||||||
|  |                     for addr in addr_info { | ||||||
|  |                         if let Some(addr_obj) = addr.as_object() | ||||||
|  |                             && let Some(ip) = addr_obj.get("local").and_then(|v| v.as_str()) | ||||||
|  |                         { | ||||||
|  |                             // Skip link-local addresses
 | ||||||
|  |                             if !ip.starts_with("fe80::") { | ||||||
|  |                                 ipv6.push(ip.to_string()); | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         (ipv4, ipv6) | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										29
									
								
								harmony_inventory_agent/src/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								harmony_inventory_agent/src/main.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | |||||||
|  | // src/main.rs
 | ||||||
|  | use actix_web::{App, HttpServer, Responder, get}; | ||||||
|  | use hwinfo::PhysicalHost; | ||||||
|  | use std::env; | ||||||
|  | 
 | ||||||
|  | mod hwinfo; | ||||||
|  | 
 | ||||||
|  | #[get("/inventory")] | ||||||
|  | async fn inventory() -> impl Responder { | ||||||
|  |     log::info!("Received inventory request"); | ||||||
|  |     let host = PhysicalHost::gather(); | ||||||
|  |     log::info!("Inventory data gathered successfully"); | ||||||
|  |     actix_web::HttpResponse::Ok().json(host) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[actix_web::main] | ||||||
|  | async fn main() -> std::io::Result<()> { | ||||||
|  |     env_logger::init(); | ||||||
|  | 
 | ||||||
|  |     let port = env::var("HARMONY_INVENTORY_AGENT_PORT").unwrap_or_else(|_| "8080".to_string()); | ||||||
|  |     let bind_addr = format!("0.0.0.0:{}", port); | ||||||
|  | 
 | ||||||
|  |     log::info!("Starting inventory agent on {}", bind_addr); | ||||||
|  | 
 | ||||||
|  |     HttpServer::new(|| App::new().service(inventory)) | ||||||
|  |         .bind(&bind_addr)? | ||||||
|  |         .run() | ||||||
|  |         .await | ||||||
|  | } | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user