├── .github └── workflows │ ├── build-test.yml │ └── release.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── ares-device ├── Cargo.toml ├── ares-device.manifest ├── build.rs └── src │ ├── main.rs │ └── picker │ ├── gtk │ └── mod.rs │ ├── mod.rs │ └── windows │ └── mod.rs ├── ares-install ├── Cargo.toml └── src │ ├── install.rs │ ├── list.rs │ ├── main.rs │ └── remove.rs ├── ares-launch ├── Cargo.toml └── src │ ├── close.rs │ ├── launch.rs │ ├── main.rs │ └── running.rs ├── ares-package ├── Cargo.toml └── src │ ├── input │ ├── app.rs │ ├── data.rs │ ├── mod.rs │ ├── service.rs │ └── validation.rs │ ├── main.rs │ └── packaging │ ├── control.rs │ ├── data.rs │ ├── header.rs │ └── mod.rs ├── ares-push ├── Cargo.toml └── src │ └── main.rs ├── ares-shell ├── Cargo.toml └── src │ ├── dumb.rs │ ├── main.rs │ └── pty.rs └── common ├── connection ├── Cargo.toml └── src │ ├── lib.rs │ ├── luna │ ├── luna.rs │ ├── message.rs │ ├── mod.rs │ └── subscription.rs │ ├── session.rs │ ├── setup.rs │ └── transfer.rs └── device ├── Cargo.toml └── src ├── device.rs ├── io.rs ├── lib.rs ├── manager.rs └── privkey.rs /.github/workflows/build-test.yml: -------------------------------------------------------------------------------- 1 | name: Build Test 2 | 3 | on: 4 | push: 5 | # Don't run for tags 6 | tags-ignore: 7 | - 'v**' 8 | branches: 9 | - '**' 10 | 11 | jobs: 12 | build-ubuntu: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | submodules: recursive 19 | 20 | - name: Install Build Dependencies 21 | run: sudo apt install -yq libgtk-3-dev 22 | 23 | - name: Use Rust Stable 24 | run: rustup toolchain install stable --profile minimal 25 | 26 | - uses: Swatinem/rust-cache@v2 27 | with: 28 | shared-key: ${{ runner.os }} 29 | 30 | - name: Install cargo-deb 31 | run: cargo install cargo-deb 32 | 33 | - name: Build Packages 34 | run: | 35 | for f in ares-*; do 36 | cargo deb -p "$f" || exit 1 37 | done -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | run-name: "Release ${{ github.ref_name }}" 3 | 4 | on: 5 | release: 6 | types: [ released ] 7 | 8 | jobs: 9 | release-ubuntu: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | with: 15 | submodules: recursive 16 | 17 | - name: Update Packages 18 | shell: bash 19 | run: sudo apt-get -yq update 20 | 21 | - name: Install Build Dependencies 22 | run: sudo apt-get install -yq libgtk-3-dev 23 | 24 | - name: Use Rust Stable 25 | run: rustup toolchain install stable --profile minimal 26 | 27 | - uses: Swatinem/rust-cache@v2 28 | with: 29 | shared-key: ${{ runner.os }} 30 | 31 | - name: Install cargo-deb 32 | run: cargo install cargo-deb 33 | 34 | - name: Build Packages 35 | run: | 36 | for f in ares-*; do 37 | cargo deb -p "$f" || exit 1 38 | done 39 | 40 | - name: Create Release (Ubuntu) 41 | id: create_release_ubuntu 42 | uses: ncipollo/release-action@v1 43 | with: 44 | token: ${{ secrets.GITHUB_TOKEN }} 45 | name: Release ${{ github.ref_name }} 46 | allowUpdates: true 47 | omitNameDuringUpdate: true 48 | omitBodyDuringUpdate: true 49 | omitPrereleaseDuringUpdate: true 50 | artifacts: target/debian/*.deb -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /.idea 3 | /.vs 4 | /.vscode -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.1.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "anstream" 31 | version = "0.6.18" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 34 | dependencies = [ 35 | "anstyle", 36 | "anstyle-parse", 37 | "anstyle-query", 38 | "anstyle-wincon", 39 | "colorchoice", 40 | "is_terminal_polyfill", 41 | "utf8parse", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle" 46 | version = "1.0.10" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 49 | 50 | [[package]] 51 | name = "anstyle-parse" 52 | version = "0.2.6" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 55 | dependencies = [ 56 | "utf8parse", 57 | ] 58 | 59 | [[package]] 60 | name = "anstyle-query" 61 | version = "1.1.2" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 64 | dependencies = [ 65 | "windows-sys 0.59.0", 66 | ] 67 | 68 | [[package]] 69 | name = "anstyle-wincon" 70 | version = "3.0.6" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" 73 | dependencies = [ 74 | "anstyle", 75 | "windows-sys 0.59.0", 76 | ] 77 | 78 | [[package]] 79 | name = "ar" 80 | version = "0.9.0" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "d67af77d68a931ecd5cbd8a3b5987d63a1d1d1278f7f6a60ae33db485cdebb69" 83 | 84 | [[package]] 85 | name = "ares-device" 86 | version = "0.1.2" 87 | dependencies = [ 88 | "cfg-if", 89 | "clap", 90 | "common-connection", 91 | "common-device", 92 | "embed-manifest", 93 | "gtk", 94 | "libssh-rs-sys", 95 | "native-windows-derive", 96 | "native-windows-gui", 97 | ] 98 | 99 | [[package]] 100 | name = "ares-install" 101 | version = "0.1.5" 102 | dependencies = [ 103 | "clap", 104 | "common-connection", 105 | "common-device", 106 | "indicatif", 107 | "libssh-rs", 108 | "regex", 109 | "serde", 110 | "serde_json", 111 | "sha256", 112 | ] 113 | 114 | [[package]] 115 | name = "ares-launch" 116 | version = "0.1.2" 117 | dependencies = [ 118 | "clap", 119 | "common-connection", 120 | "common-device", 121 | "libssh-rs", 122 | "serde", 123 | "serde_json", 124 | ] 125 | 126 | [[package]] 127 | name = "ares-package" 128 | version = "0.1.4" 129 | dependencies = [ 130 | "ar", 131 | "clap", 132 | "elf", 133 | "flate2", 134 | "path-slash", 135 | "regex", 136 | "serde", 137 | "serde_json", 138 | "tar", 139 | "walkdir", 140 | ] 141 | 142 | [[package]] 143 | name = "ares-push" 144 | version = "0.1.4" 145 | dependencies = [ 146 | "clap", 147 | "common-connection", 148 | "common-device", 149 | "libssh-rs", 150 | "path-slash", 151 | "walkdir", 152 | ] 153 | 154 | [[package]] 155 | name = "ares-shell" 156 | version = "0.1.1" 157 | dependencies = [ 158 | "clap", 159 | "common-connection", 160 | "common-device", 161 | "crossbeam-channel", 162 | "crossterm", 163 | "libssh-rs", 164 | ] 165 | 166 | [[package]] 167 | name = "async-trait" 168 | version = "0.1.83" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" 171 | dependencies = [ 172 | "proc-macro2", 173 | "quote", 174 | "syn 2.0.87", 175 | ] 176 | 177 | [[package]] 178 | name = "atk" 179 | version = "0.18.0" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "b4af014b17dd80e8af9fa689b2d4a211ddba6eb583c1622f35d0cb543f6b17e4" 182 | dependencies = [ 183 | "atk-sys", 184 | "glib", 185 | "libc", 186 | ] 187 | 188 | [[package]] 189 | name = "atk-sys" 190 | version = "0.18.0" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "251e0b7d90e33e0ba930891a505a9a35ece37b2dd37a14f3ffc306c13b980009" 193 | dependencies = [ 194 | "glib-sys", 195 | "gobject-sys", 196 | "libc", 197 | "system-deps", 198 | ] 199 | 200 | [[package]] 201 | name = "atomic-waker" 202 | version = "1.1.2" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 205 | 206 | [[package]] 207 | name = "autocfg" 208 | version = "1.4.0" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 211 | 212 | [[package]] 213 | name = "backtrace" 214 | version = "0.3.74" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 217 | dependencies = [ 218 | "addr2line", 219 | "cfg-if", 220 | "libc", 221 | "miniz_oxide", 222 | "object", 223 | "rustc-demangle", 224 | "windows-targets", 225 | ] 226 | 227 | [[package]] 228 | name = "base64" 229 | version = "0.22.1" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 232 | 233 | [[package]] 234 | name = "bitflags" 235 | version = "1.3.2" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 238 | 239 | [[package]] 240 | name = "bitflags" 241 | version = "2.6.0" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 244 | 245 | [[package]] 246 | name = "block-buffer" 247 | version = "0.10.4" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 250 | dependencies = [ 251 | "generic-array", 252 | ] 253 | 254 | [[package]] 255 | name = "bumpalo" 256 | version = "3.16.0" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 259 | 260 | [[package]] 261 | name = "bytes" 262 | version = "1.8.0" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" 265 | 266 | [[package]] 267 | name = "cairo-rs" 268 | version = "0.18.5" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" 271 | dependencies = [ 272 | "bitflags 2.6.0", 273 | "cairo-sys-rs", 274 | "glib", 275 | "libc", 276 | "once_cell", 277 | "thiserror", 278 | ] 279 | 280 | [[package]] 281 | name = "cairo-sys-rs" 282 | version = "0.18.2" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" 285 | dependencies = [ 286 | "glib-sys", 287 | "libc", 288 | "system-deps", 289 | ] 290 | 291 | [[package]] 292 | name = "cc" 293 | version = "1.1.37" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "40545c26d092346d8a8dab71ee48e7685a7a9cba76e634790c215b41a4a7b4cf" 296 | dependencies = [ 297 | "shlex", 298 | ] 299 | 300 | [[package]] 301 | name = "cfg-expr" 302 | version = "0.15.8" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" 305 | dependencies = [ 306 | "smallvec", 307 | "target-lexicon", 308 | ] 309 | 310 | [[package]] 311 | name = "cfg-if" 312 | version = "1.0.0" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 315 | 316 | [[package]] 317 | name = "clap" 318 | version = "4.5.20" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" 321 | dependencies = [ 322 | "clap_builder", 323 | "clap_derive", 324 | ] 325 | 326 | [[package]] 327 | name = "clap_builder" 328 | version = "4.5.20" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" 331 | dependencies = [ 332 | "anstream", 333 | "anstyle", 334 | "clap_lex", 335 | "strsim", 336 | ] 337 | 338 | [[package]] 339 | name = "clap_derive" 340 | version = "4.5.18" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" 343 | dependencies = [ 344 | "heck 0.5.0", 345 | "proc-macro2", 346 | "quote", 347 | "syn 2.0.87", 348 | ] 349 | 350 | [[package]] 351 | name = "clap_lex" 352 | version = "0.7.2" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" 355 | 356 | [[package]] 357 | name = "colorchoice" 358 | version = "1.0.3" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 361 | 362 | [[package]] 363 | name = "common-connection" 364 | version = "0.1.1" 365 | dependencies = [ 366 | "common-device", 367 | "libssh-rs", 368 | "libssh-rs-sys", 369 | "path-slash", 370 | "reqwest", 371 | "serde", 372 | "serde_json", 373 | "snailquote", 374 | ] 375 | 376 | [[package]] 377 | name = "common-device" 378 | version = "0.1.1" 379 | dependencies = [ 380 | "home", 381 | "log", 382 | "pathdiff", 383 | "serde", 384 | "serde_json", 385 | ] 386 | 387 | [[package]] 388 | name = "console" 389 | version = "0.15.8" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" 392 | dependencies = [ 393 | "encode_unicode", 394 | "lazy_static", 395 | "libc", 396 | "unicode-width", 397 | "windows-sys 0.52.0", 398 | ] 399 | 400 | [[package]] 401 | name = "core-foundation" 402 | version = "0.9.4" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 405 | dependencies = [ 406 | "core-foundation-sys", 407 | "libc", 408 | ] 409 | 410 | [[package]] 411 | name = "core-foundation-sys" 412 | version = "0.8.7" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 415 | 416 | [[package]] 417 | name = "cpufeatures" 418 | version = "0.2.14" 419 | source = "registry+https://github.com/rust-lang/crates.io-index" 420 | checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" 421 | dependencies = [ 422 | "libc", 423 | ] 424 | 425 | [[package]] 426 | name = "crc32fast" 427 | version = "1.4.2" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" 430 | dependencies = [ 431 | "cfg-if", 432 | ] 433 | 434 | [[package]] 435 | name = "crossbeam-channel" 436 | version = "0.5.13" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" 439 | dependencies = [ 440 | "crossbeam-utils", 441 | ] 442 | 443 | [[package]] 444 | name = "crossbeam-utils" 445 | version = "0.8.20" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" 448 | 449 | [[package]] 450 | name = "crossterm" 451 | version = "0.28.1" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" 454 | dependencies = [ 455 | "bitflags 2.6.0", 456 | "crossterm_winapi", 457 | "mio", 458 | "parking_lot", 459 | "rustix", 460 | "signal-hook", 461 | "signal-hook-mio", 462 | "winapi", 463 | ] 464 | 465 | [[package]] 466 | name = "crossterm_winapi" 467 | version = "0.9.1" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" 470 | dependencies = [ 471 | "winapi", 472 | ] 473 | 474 | [[package]] 475 | name = "crypto-common" 476 | version = "0.1.6" 477 | source = "registry+https://github.com/rust-lang/crates.io-index" 478 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 479 | dependencies = [ 480 | "generic-array", 481 | "typenum", 482 | ] 483 | 484 | [[package]] 485 | name = "digest" 486 | version = "0.10.7" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 489 | dependencies = [ 490 | "block-buffer", 491 | "crypto-common", 492 | ] 493 | 494 | [[package]] 495 | name = "displaydoc" 496 | version = "0.2.5" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 499 | dependencies = [ 500 | "proc-macro2", 501 | "quote", 502 | "syn 2.0.87", 503 | ] 504 | 505 | [[package]] 506 | name = "elf" 507 | version = "0.7.4" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b" 510 | 511 | [[package]] 512 | name = "embed-manifest" 513 | version = "1.4.0" 514 | source = "registry+https://github.com/rust-lang/crates.io-index" 515 | checksum = "41cd446c890d6bed1d8b53acef5f240069ebef91d6fae7c5f52efe61fe8b5eae" 516 | 517 | [[package]] 518 | name = "encode_unicode" 519 | version = "0.3.6" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 522 | 523 | [[package]] 524 | name = "encoding_rs" 525 | version = "0.8.35" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" 528 | dependencies = [ 529 | "cfg-if", 530 | ] 531 | 532 | [[package]] 533 | name = "equivalent" 534 | version = "1.0.1" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 537 | 538 | [[package]] 539 | name = "errno" 540 | version = "0.3.9" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 543 | dependencies = [ 544 | "libc", 545 | "windows-sys 0.52.0", 546 | ] 547 | 548 | [[package]] 549 | name = "fastrand" 550 | version = "2.2.0" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" 553 | 554 | [[package]] 555 | name = "field-offset" 556 | version = "0.3.6" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" 559 | dependencies = [ 560 | "memoffset", 561 | "rustc_version", 562 | ] 563 | 564 | [[package]] 565 | name = "filetime" 566 | version = "0.2.25" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" 569 | dependencies = [ 570 | "cfg-if", 571 | "libc", 572 | "libredox", 573 | "windows-sys 0.59.0", 574 | ] 575 | 576 | [[package]] 577 | name = "flate2" 578 | version = "1.0.34" 579 | source = "registry+https://github.com/rust-lang/crates.io-index" 580 | checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" 581 | dependencies = [ 582 | "crc32fast", 583 | "miniz_oxide", 584 | ] 585 | 586 | [[package]] 587 | name = "fnv" 588 | version = "1.0.7" 589 | source = "registry+https://github.com/rust-lang/crates.io-index" 590 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 591 | 592 | [[package]] 593 | name = "foreign-types" 594 | version = "0.3.2" 595 | source = "registry+https://github.com/rust-lang/crates.io-index" 596 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 597 | dependencies = [ 598 | "foreign-types-shared", 599 | ] 600 | 601 | [[package]] 602 | name = "foreign-types-shared" 603 | version = "0.1.1" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 606 | 607 | [[package]] 608 | name = "form_urlencoded" 609 | version = "1.2.1" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 612 | dependencies = [ 613 | "percent-encoding", 614 | ] 615 | 616 | [[package]] 617 | name = "futures-channel" 618 | version = "0.3.31" 619 | source = "registry+https://github.com/rust-lang/crates.io-index" 620 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 621 | dependencies = [ 622 | "futures-core", 623 | "futures-sink", 624 | ] 625 | 626 | [[package]] 627 | name = "futures-core" 628 | version = "0.3.31" 629 | source = "registry+https://github.com/rust-lang/crates.io-index" 630 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 631 | 632 | [[package]] 633 | name = "futures-executor" 634 | version = "0.3.31" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" 637 | dependencies = [ 638 | "futures-core", 639 | "futures-task", 640 | "futures-util", 641 | ] 642 | 643 | [[package]] 644 | name = "futures-io" 645 | version = "0.3.31" 646 | source = "registry+https://github.com/rust-lang/crates.io-index" 647 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 648 | 649 | [[package]] 650 | name = "futures-macro" 651 | version = "0.3.31" 652 | source = "registry+https://github.com/rust-lang/crates.io-index" 653 | checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 654 | dependencies = [ 655 | "proc-macro2", 656 | "quote", 657 | "syn 2.0.87", 658 | ] 659 | 660 | [[package]] 661 | name = "futures-sink" 662 | version = "0.3.31" 663 | source = "registry+https://github.com/rust-lang/crates.io-index" 664 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 665 | 666 | [[package]] 667 | name = "futures-task" 668 | version = "0.3.31" 669 | source = "registry+https://github.com/rust-lang/crates.io-index" 670 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 671 | 672 | [[package]] 673 | name = "futures-util" 674 | version = "0.3.31" 675 | source = "registry+https://github.com/rust-lang/crates.io-index" 676 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 677 | dependencies = [ 678 | "futures-core", 679 | "futures-io", 680 | "futures-macro", 681 | "futures-sink", 682 | "futures-task", 683 | "memchr", 684 | "pin-project-lite", 685 | "pin-utils", 686 | "slab", 687 | ] 688 | 689 | [[package]] 690 | name = "gdk" 691 | version = "0.18.0" 692 | source = "registry+https://github.com/rust-lang/crates.io-index" 693 | checksum = "f5ba081bdef3b75ebcdbfc953699ed2d7417d6bd853347a42a37d76406a33646" 694 | dependencies = [ 695 | "cairo-rs", 696 | "gdk-pixbuf", 697 | "gdk-sys", 698 | "gio", 699 | "glib", 700 | "libc", 701 | "pango", 702 | ] 703 | 704 | [[package]] 705 | name = "gdk-pixbuf" 706 | version = "0.18.5" 707 | source = "registry+https://github.com/rust-lang/crates.io-index" 708 | checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" 709 | dependencies = [ 710 | "gdk-pixbuf-sys", 711 | "gio", 712 | "glib", 713 | "libc", 714 | "once_cell", 715 | ] 716 | 717 | [[package]] 718 | name = "gdk-pixbuf-sys" 719 | version = "0.18.0" 720 | source = "registry+https://github.com/rust-lang/crates.io-index" 721 | checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" 722 | dependencies = [ 723 | "gio-sys", 724 | "glib-sys", 725 | "gobject-sys", 726 | "libc", 727 | "system-deps", 728 | ] 729 | 730 | [[package]] 731 | name = "gdk-sys" 732 | version = "0.18.0" 733 | source = "registry+https://github.com/rust-lang/crates.io-index" 734 | checksum = "31ff856cb3386dae1703a920f803abafcc580e9b5f711ca62ed1620c25b51ff2" 735 | dependencies = [ 736 | "cairo-sys-rs", 737 | "gdk-pixbuf-sys", 738 | "gio-sys", 739 | "glib-sys", 740 | "gobject-sys", 741 | "libc", 742 | "pango-sys", 743 | "pkg-config", 744 | "system-deps", 745 | ] 746 | 747 | [[package]] 748 | name = "generic-array" 749 | version = "0.14.7" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 752 | dependencies = [ 753 | "typenum", 754 | "version_check", 755 | ] 756 | 757 | [[package]] 758 | name = "getrandom" 759 | version = "0.2.15" 760 | source = "registry+https://github.com/rust-lang/crates.io-index" 761 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 762 | dependencies = [ 763 | "cfg-if", 764 | "libc", 765 | "wasi", 766 | ] 767 | 768 | [[package]] 769 | name = "gimli" 770 | version = "0.31.1" 771 | source = "registry+https://github.com/rust-lang/crates.io-index" 772 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 773 | 774 | [[package]] 775 | name = "gio" 776 | version = "0.18.4" 777 | source = "registry+https://github.com/rust-lang/crates.io-index" 778 | checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" 779 | dependencies = [ 780 | "futures-channel", 781 | "futures-core", 782 | "futures-io", 783 | "futures-util", 784 | "gio-sys", 785 | "glib", 786 | "libc", 787 | "once_cell", 788 | "pin-project-lite", 789 | "smallvec", 790 | "thiserror", 791 | ] 792 | 793 | [[package]] 794 | name = "gio-sys" 795 | version = "0.18.1" 796 | source = "registry+https://github.com/rust-lang/crates.io-index" 797 | checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" 798 | dependencies = [ 799 | "glib-sys", 800 | "gobject-sys", 801 | "libc", 802 | "system-deps", 803 | "winapi", 804 | ] 805 | 806 | [[package]] 807 | name = "glib" 808 | version = "0.18.5" 809 | source = "registry+https://github.com/rust-lang/crates.io-index" 810 | checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" 811 | dependencies = [ 812 | "bitflags 2.6.0", 813 | "futures-channel", 814 | "futures-core", 815 | "futures-executor", 816 | "futures-task", 817 | "futures-util", 818 | "gio-sys", 819 | "glib-macros", 820 | "glib-sys", 821 | "gobject-sys", 822 | "libc", 823 | "memchr", 824 | "once_cell", 825 | "smallvec", 826 | "thiserror", 827 | ] 828 | 829 | [[package]] 830 | name = "glib-macros" 831 | version = "0.18.5" 832 | source = "registry+https://github.com/rust-lang/crates.io-index" 833 | checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" 834 | dependencies = [ 835 | "heck 0.4.1", 836 | "proc-macro-crate 2.0.2", 837 | "proc-macro-error", 838 | "proc-macro2", 839 | "quote", 840 | "syn 2.0.87", 841 | ] 842 | 843 | [[package]] 844 | name = "glib-sys" 845 | version = "0.18.1" 846 | source = "registry+https://github.com/rust-lang/crates.io-index" 847 | checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" 848 | dependencies = [ 849 | "libc", 850 | "system-deps", 851 | ] 852 | 853 | [[package]] 854 | name = "gobject-sys" 855 | version = "0.18.0" 856 | source = "registry+https://github.com/rust-lang/crates.io-index" 857 | checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" 858 | dependencies = [ 859 | "glib-sys", 860 | "libc", 861 | "system-deps", 862 | ] 863 | 864 | [[package]] 865 | name = "gtk" 866 | version = "0.18.1" 867 | source = "registry+https://github.com/rust-lang/crates.io-index" 868 | checksum = "93c4f5e0e20b60e10631a5f06da7fe3dda744b05ad0ea71fee2f47adf865890c" 869 | dependencies = [ 870 | "atk", 871 | "cairo-rs", 872 | "field-offset", 873 | "futures-channel", 874 | "gdk", 875 | "gdk-pixbuf", 876 | "gio", 877 | "glib", 878 | "gtk-sys", 879 | "gtk3-macros", 880 | "libc", 881 | "pango", 882 | "pkg-config", 883 | ] 884 | 885 | [[package]] 886 | name = "gtk-sys" 887 | version = "0.18.0" 888 | source = "registry+https://github.com/rust-lang/crates.io-index" 889 | checksum = "771437bf1de2c1c0b496c11505bdf748e26066bbe942dfc8f614c9460f6d7722" 890 | dependencies = [ 891 | "atk-sys", 892 | "cairo-sys-rs", 893 | "gdk-pixbuf-sys", 894 | "gdk-sys", 895 | "gio-sys", 896 | "glib-sys", 897 | "gobject-sys", 898 | "libc", 899 | "pango-sys", 900 | "system-deps", 901 | ] 902 | 903 | [[package]] 904 | name = "gtk3-macros" 905 | version = "0.18.0" 906 | source = "registry+https://github.com/rust-lang/crates.io-index" 907 | checksum = "c6063efb63db582968fb7df72e1ae68aa6360dcfb0a75143f34fc7d616bad75e" 908 | dependencies = [ 909 | "proc-macro-crate 1.3.1", 910 | "proc-macro-error", 911 | "proc-macro2", 912 | "quote", 913 | "syn 2.0.87", 914 | ] 915 | 916 | [[package]] 917 | name = "h2" 918 | version = "0.4.6" 919 | source = "registry+https://github.com/rust-lang/crates.io-index" 920 | checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" 921 | dependencies = [ 922 | "atomic-waker", 923 | "bytes", 924 | "fnv", 925 | "futures-core", 926 | "futures-sink", 927 | "http", 928 | "indexmap", 929 | "slab", 930 | "tokio", 931 | "tokio-util", 932 | "tracing", 933 | ] 934 | 935 | [[package]] 936 | name = "hashbrown" 937 | version = "0.15.1" 938 | source = "registry+https://github.com/rust-lang/crates.io-index" 939 | checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" 940 | 941 | [[package]] 942 | name = "heck" 943 | version = "0.4.1" 944 | source = "registry+https://github.com/rust-lang/crates.io-index" 945 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 946 | 947 | [[package]] 948 | name = "heck" 949 | version = "0.5.0" 950 | source = "registry+https://github.com/rust-lang/crates.io-index" 951 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 952 | 953 | [[package]] 954 | name = "hermit-abi" 955 | version = "0.3.9" 956 | source = "registry+https://github.com/rust-lang/crates.io-index" 957 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 958 | 959 | [[package]] 960 | name = "hex" 961 | version = "0.4.3" 962 | source = "registry+https://github.com/rust-lang/crates.io-index" 963 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 964 | 965 | [[package]] 966 | name = "home" 967 | version = "0.5.9" 968 | source = "registry+https://github.com/rust-lang/crates.io-index" 969 | checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" 970 | dependencies = [ 971 | "windows-sys 0.52.0", 972 | ] 973 | 974 | [[package]] 975 | name = "http" 976 | version = "1.1.0" 977 | source = "registry+https://github.com/rust-lang/crates.io-index" 978 | checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" 979 | dependencies = [ 980 | "bytes", 981 | "fnv", 982 | "itoa", 983 | ] 984 | 985 | [[package]] 986 | name = "http-body" 987 | version = "1.0.1" 988 | source = "registry+https://github.com/rust-lang/crates.io-index" 989 | checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 990 | dependencies = [ 991 | "bytes", 992 | "http", 993 | ] 994 | 995 | [[package]] 996 | name = "http-body-util" 997 | version = "0.1.2" 998 | source = "registry+https://github.com/rust-lang/crates.io-index" 999 | checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" 1000 | dependencies = [ 1001 | "bytes", 1002 | "futures-util", 1003 | "http", 1004 | "http-body", 1005 | "pin-project-lite", 1006 | ] 1007 | 1008 | [[package]] 1009 | name = "httparse" 1010 | version = "1.9.5" 1011 | source = "registry+https://github.com/rust-lang/crates.io-index" 1012 | checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" 1013 | 1014 | [[package]] 1015 | name = "hyper" 1016 | version = "1.5.0" 1017 | source = "registry+https://github.com/rust-lang/crates.io-index" 1018 | checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" 1019 | dependencies = [ 1020 | "bytes", 1021 | "futures-channel", 1022 | "futures-util", 1023 | "h2", 1024 | "http", 1025 | "http-body", 1026 | "httparse", 1027 | "itoa", 1028 | "pin-project-lite", 1029 | "smallvec", 1030 | "tokio", 1031 | "want", 1032 | ] 1033 | 1034 | [[package]] 1035 | name = "hyper-rustls" 1036 | version = "0.27.3" 1037 | source = "registry+https://github.com/rust-lang/crates.io-index" 1038 | checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" 1039 | dependencies = [ 1040 | "futures-util", 1041 | "http", 1042 | "hyper", 1043 | "hyper-util", 1044 | "rustls", 1045 | "rustls-pki-types", 1046 | "tokio", 1047 | "tokio-rustls", 1048 | "tower-service", 1049 | ] 1050 | 1051 | [[package]] 1052 | name = "hyper-tls" 1053 | version = "0.6.0" 1054 | source = "registry+https://github.com/rust-lang/crates.io-index" 1055 | checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" 1056 | dependencies = [ 1057 | "bytes", 1058 | "http-body-util", 1059 | "hyper", 1060 | "hyper-util", 1061 | "native-tls", 1062 | "tokio", 1063 | "tokio-native-tls", 1064 | "tower-service", 1065 | ] 1066 | 1067 | [[package]] 1068 | name = "hyper-util" 1069 | version = "0.1.10" 1070 | source = "registry+https://github.com/rust-lang/crates.io-index" 1071 | checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" 1072 | dependencies = [ 1073 | "bytes", 1074 | "futures-channel", 1075 | "futures-util", 1076 | "http", 1077 | "http-body", 1078 | "hyper", 1079 | "pin-project-lite", 1080 | "socket2", 1081 | "tokio", 1082 | "tower-service", 1083 | "tracing", 1084 | ] 1085 | 1086 | [[package]] 1087 | name = "icu_collections" 1088 | version = "1.5.0" 1089 | source = "registry+https://github.com/rust-lang/crates.io-index" 1090 | checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" 1091 | dependencies = [ 1092 | "displaydoc", 1093 | "yoke", 1094 | "zerofrom", 1095 | "zerovec", 1096 | ] 1097 | 1098 | [[package]] 1099 | name = "icu_locid" 1100 | version = "1.5.0" 1101 | source = "registry+https://github.com/rust-lang/crates.io-index" 1102 | checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" 1103 | dependencies = [ 1104 | "displaydoc", 1105 | "litemap", 1106 | "tinystr", 1107 | "writeable", 1108 | "zerovec", 1109 | ] 1110 | 1111 | [[package]] 1112 | name = "icu_locid_transform" 1113 | version = "1.5.0" 1114 | source = "registry+https://github.com/rust-lang/crates.io-index" 1115 | checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" 1116 | dependencies = [ 1117 | "displaydoc", 1118 | "icu_locid", 1119 | "icu_locid_transform_data", 1120 | "icu_provider", 1121 | "tinystr", 1122 | "zerovec", 1123 | ] 1124 | 1125 | [[package]] 1126 | name = "icu_locid_transform_data" 1127 | version = "1.5.0" 1128 | source = "registry+https://github.com/rust-lang/crates.io-index" 1129 | checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" 1130 | 1131 | [[package]] 1132 | name = "icu_normalizer" 1133 | version = "1.5.0" 1134 | source = "registry+https://github.com/rust-lang/crates.io-index" 1135 | checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" 1136 | dependencies = [ 1137 | "displaydoc", 1138 | "icu_collections", 1139 | "icu_normalizer_data", 1140 | "icu_properties", 1141 | "icu_provider", 1142 | "smallvec", 1143 | "utf16_iter", 1144 | "utf8_iter", 1145 | "write16", 1146 | "zerovec", 1147 | ] 1148 | 1149 | [[package]] 1150 | name = "icu_normalizer_data" 1151 | version = "1.5.0" 1152 | source = "registry+https://github.com/rust-lang/crates.io-index" 1153 | checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" 1154 | 1155 | [[package]] 1156 | name = "icu_properties" 1157 | version = "1.5.1" 1158 | source = "registry+https://github.com/rust-lang/crates.io-index" 1159 | checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" 1160 | dependencies = [ 1161 | "displaydoc", 1162 | "icu_collections", 1163 | "icu_locid_transform", 1164 | "icu_properties_data", 1165 | "icu_provider", 1166 | "tinystr", 1167 | "zerovec", 1168 | ] 1169 | 1170 | [[package]] 1171 | name = "icu_properties_data" 1172 | version = "1.5.0" 1173 | source = "registry+https://github.com/rust-lang/crates.io-index" 1174 | checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" 1175 | 1176 | [[package]] 1177 | name = "icu_provider" 1178 | version = "1.5.0" 1179 | source = "registry+https://github.com/rust-lang/crates.io-index" 1180 | checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" 1181 | dependencies = [ 1182 | "displaydoc", 1183 | "icu_locid", 1184 | "icu_provider_macros", 1185 | "stable_deref_trait", 1186 | "tinystr", 1187 | "writeable", 1188 | "yoke", 1189 | "zerofrom", 1190 | "zerovec", 1191 | ] 1192 | 1193 | [[package]] 1194 | name = "icu_provider_macros" 1195 | version = "1.5.0" 1196 | source = "registry+https://github.com/rust-lang/crates.io-index" 1197 | checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" 1198 | dependencies = [ 1199 | "proc-macro2", 1200 | "quote", 1201 | "syn 2.0.87", 1202 | ] 1203 | 1204 | [[package]] 1205 | name = "idna" 1206 | version = "1.0.3" 1207 | source = "registry+https://github.com/rust-lang/crates.io-index" 1208 | checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" 1209 | dependencies = [ 1210 | "idna_adapter", 1211 | "smallvec", 1212 | "utf8_iter", 1213 | ] 1214 | 1215 | [[package]] 1216 | name = "idna_adapter" 1217 | version = "1.2.0" 1218 | source = "registry+https://github.com/rust-lang/crates.io-index" 1219 | checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" 1220 | dependencies = [ 1221 | "icu_normalizer", 1222 | "icu_properties", 1223 | ] 1224 | 1225 | [[package]] 1226 | name = "indexmap" 1227 | version = "2.6.0" 1228 | source = "registry+https://github.com/rust-lang/crates.io-index" 1229 | checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" 1230 | dependencies = [ 1231 | "equivalent", 1232 | "hashbrown", 1233 | ] 1234 | 1235 | [[package]] 1236 | name = "indicatif" 1237 | version = "0.17.8" 1238 | source = "registry+https://github.com/rust-lang/crates.io-index" 1239 | checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" 1240 | dependencies = [ 1241 | "console", 1242 | "instant", 1243 | "number_prefix", 1244 | "portable-atomic", 1245 | "unicode-width", 1246 | ] 1247 | 1248 | [[package]] 1249 | name = "instant" 1250 | version = "0.1.13" 1251 | source = "registry+https://github.com/rust-lang/crates.io-index" 1252 | checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" 1253 | dependencies = [ 1254 | "cfg-if", 1255 | ] 1256 | 1257 | [[package]] 1258 | name = "ipnet" 1259 | version = "2.10.1" 1260 | source = "registry+https://github.com/rust-lang/crates.io-index" 1261 | checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" 1262 | 1263 | [[package]] 1264 | name = "is_terminal_polyfill" 1265 | version = "1.70.1" 1266 | source = "registry+https://github.com/rust-lang/crates.io-index" 1267 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 1268 | 1269 | [[package]] 1270 | name = "itoa" 1271 | version = "1.0.11" 1272 | source = "registry+https://github.com/rust-lang/crates.io-index" 1273 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 1274 | 1275 | [[package]] 1276 | name = "js-sys" 1277 | version = "0.3.72" 1278 | source = "registry+https://github.com/rust-lang/crates.io-index" 1279 | checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" 1280 | dependencies = [ 1281 | "wasm-bindgen", 1282 | ] 1283 | 1284 | [[package]] 1285 | name = "lazy_static" 1286 | version = "1.5.0" 1287 | source = "registry+https://github.com/rust-lang/crates.io-index" 1288 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 1289 | 1290 | [[package]] 1291 | name = "libc" 1292 | version = "0.2.162" 1293 | source = "registry+https://github.com/rust-lang/crates.io-index" 1294 | checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" 1295 | 1296 | [[package]] 1297 | name = "libm" 1298 | version = "0.1.4" 1299 | source = "registry+https://github.com/rust-lang/crates.io-index" 1300 | checksum = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a" 1301 | 1302 | [[package]] 1303 | name = "libredox" 1304 | version = "0.1.3" 1305 | source = "registry+https://github.com/rust-lang/crates.io-index" 1306 | checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" 1307 | dependencies = [ 1308 | "bitflags 2.6.0", 1309 | "libc", 1310 | "redox_syscall", 1311 | ] 1312 | 1313 | [[package]] 1314 | name = "libssh-rs" 1315 | version = "0.3.3" 1316 | source = "registry+https://github.com/rust-lang/crates.io-index" 1317 | checksum = "4d46682cfd8dc53c52471cfed5eedcc9248c318f3c8df174ec1b8c4f41565f98" 1318 | dependencies = [ 1319 | "bitflags 1.3.2", 1320 | "libc", 1321 | "libssh-rs-sys", 1322 | "openssl-sys", 1323 | "thiserror", 1324 | ] 1325 | 1326 | [[package]] 1327 | name = "libssh-rs-sys" 1328 | version = "0.2.4" 1329 | source = "registry+https://github.com/rust-lang/crates.io-index" 1330 | checksum = "205ceca5947f473c76f34175de70b15d9e8fadbbdb1bba45d4973037bbdb5fc7" 1331 | dependencies = [ 1332 | "cc", 1333 | "libz-sys", 1334 | "openssl-sys", 1335 | "pkg-config", 1336 | ] 1337 | 1338 | [[package]] 1339 | name = "libz-sys" 1340 | version = "1.1.20" 1341 | source = "registry+https://github.com/rust-lang/crates.io-index" 1342 | checksum = "d2d16453e800a8cf6dd2fc3eb4bc99b786a9b90c663b8559a5b1a041bf89e472" 1343 | dependencies = [ 1344 | "cc", 1345 | "libc", 1346 | "pkg-config", 1347 | "vcpkg", 1348 | ] 1349 | 1350 | [[package]] 1351 | name = "linux-raw-sys" 1352 | version = "0.4.14" 1353 | source = "registry+https://github.com/rust-lang/crates.io-index" 1354 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 1355 | 1356 | [[package]] 1357 | name = "litemap" 1358 | version = "0.7.3" 1359 | source = "registry+https://github.com/rust-lang/crates.io-index" 1360 | checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" 1361 | 1362 | [[package]] 1363 | name = "lock_api" 1364 | version = "0.4.12" 1365 | source = "registry+https://github.com/rust-lang/crates.io-index" 1366 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 1367 | dependencies = [ 1368 | "autocfg", 1369 | "scopeguard", 1370 | ] 1371 | 1372 | [[package]] 1373 | name = "log" 1374 | version = "0.4.22" 1375 | source = "registry+https://github.com/rust-lang/crates.io-index" 1376 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 1377 | 1378 | [[package]] 1379 | name = "memchr" 1380 | version = "2.7.4" 1381 | source = "registry+https://github.com/rust-lang/crates.io-index" 1382 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 1383 | 1384 | [[package]] 1385 | name = "memoffset" 1386 | version = "0.9.1" 1387 | source = "registry+https://github.com/rust-lang/crates.io-index" 1388 | checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" 1389 | dependencies = [ 1390 | "autocfg", 1391 | ] 1392 | 1393 | [[package]] 1394 | name = "mime" 1395 | version = "0.3.17" 1396 | source = "registry+https://github.com/rust-lang/crates.io-index" 1397 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 1398 | 1399 | [[package]] 1400 | name = "miniz_oxide" 1401 | version = "0.8.0" 1402 | source = "registry+https://github.com/rust-lang/crates.io-index" 1403 | checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" 1404 | dependencies = [ 1405 | "adler2", 1406 | ] 1407 | 1408 | [[package]] 1409 | name = "mio" 1410 | version = "1.0.2" 1411 | source = "registry+https://github.com/rust-lang/crates.io-index" 1412 | checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" 1413 | dependencies = [ 1414 | "hermit-abi", 1415 | "libc", 1416 | "log", 1417 | "wasi", 1418 | "windows-sys 0.52.0", 1419 | ] 1420 | 1421 | [[package]] 1422 | name = "native-tls" 1423 | version = "0.2.12" 1424 | source = "registry+https://github.com/rust-lang/crates.io-index" 1425 | checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" 1426 | dependencies = [ 1427 | "libc", 1428 | "log", 1429 | "openssl", 1430 | "openssl-probe", 1431 | "openssl-sys", 1432 | "schannel", 1433 | "security-framework", 1434 | "security-framework-sys", 1435 | "tempfile", 1436 | ] 1437 | 1438 | [[package]] 1439 | name = "native-windows-derive" 1440 | version = "1.0.5" 1441 | source = "registry+https://github.com/rust-lang/crates.io-index" 1442 | checksum = "76134ae81020d89d154f619fd2495a2cecad204276b1dc21174b55e4d0975edd" 1443 | dependencies = [ 1444 | "proc-macro-crate 0.1.5", 1445 | "proc-macro2", 1446 | "quote", 1447 | "syn 1.0.109", 1448 | ] 1449 | 1450 | [[package]] 1451 | name = "native-windows-gui" 1452 | version = "1.0.13" 1453 | source = "registry+https://github.com/rust-lang/crates.io-index" 1454 | checksum = "4f7003a669f68deb6b7c57d74fff4f8e533c44a3f0b297492440ef4ff5a28454" 1455 | dependencies = [ 1456 | "bitflags 1.3.2", 1457 | "lazy_static", 1458 | "newline-converter", 1459 | "plotters", 1460 | "plotters-backend", 1461 | "stretch", 1462 | "winapi", 1463 | "winapi-build", 1464 | ] 1465 | 1466 | [[package]] 1467 | name = "newline-converter" 1468 | version = "0.2.2" 1469 | source = "registry+https://github.com/rust-lang/crates.io-index" 1470 | checksum = "1f71d09d5c87634207f894c6b31b6a2b2c64ea3bdcf71bd5599fdbbe1600c00f" 1471 | dependencies = [ 1472 | "unicode-segmentation", 1473 | ] 1474 | 1475 | [[package]] 1476 | name = "num-traits" 1477 | version = "0.2.19" 1478 | source = "registry+https://github.com/rust-lang/crates.io-index" 1479 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 1480 | dependencies = [ 1481 | "autocfg", 1482 | ] 1483 | 1484 | [[package]] 1485 | name = "number_prefix" 1486 | version = "0.4.0" 1487 | source = "registry+https://github.com/rust-lang/crates.io-index" 1488 | checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" 1489 | 1490 | [[package]] 1491 | name = "object" 1492 | version = "0.36.5" 1493 | source = "registry+https://github.com/rust-lang/crates.io-index" 1494 | checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" 1495 | dependencies = [ 1496 | "memchr", 1497 | ] 1498 | 1499 | [[package]] 1500 | name = "once_cell" 1501 | version = "1.20.2" 1502 | source = "registry+https://github.com/rust-lang/crates.io-index" 1503 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 1504 | 1505 | [[package]] 1506 | name = "openssl" 1507 | version = "0.10.68" 1508 | source = "registry+https://github.com/rust-lang/crates.io-index" 1509 | checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" 1510 | dependencies = [ 1511 | "bitflags 2.6.0", 1512 | "cfg-if", 1513 | "foreign-types", 1514 | "libc", 1515 | "once_cell", 1516 | "openssl-macros", 1517 | "openssl-sys", 1518 | ] 1519 | 1520 | [[package]] 1521 | name = "openssl-macros" 1522 | version = "0.1.1" 1523 | source = "registry+https://github.com/rust-lang/crates.io-index" 1524 | checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 1525 | dependencies = [ 1526 | "proc-macro2", 1527 | "quote", 1528 | "syn 2.0.87", 1529 | ] 1530 | 1531 | [[package]] 1532 | name = "openssl-probe" 1533 | version = "0.1.5" 1534 | source = "registry+https://github.com/rust-lang/crates.io-index" 1535 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 1536 | 1537 | [[package]] 1538 | name = "openssl-src" 1539 | version = "300.4.0+3.4.0" 1540 | source = "registry+https://github.com/rust-lang/crates.io-index" 1541 | checksum = "a709e02f2b4aca747929cca5ed248880847c650233cf8b8cdc48f40aaf4898a6" 1542 | dependencies = [ 1543 | "cc", 1544 | ] 1545 | 1546 | [[package]] 1547 | name = "openssl-sys" 1548 | version = "0.9.104" 1549 | source = "registry+https://github.com/rust-lang/crates.io-index" 1550 | checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" 1551 | dependencies = [ 1552 | "cc", 1553 | "libc", 1554 | "openssl-src", 1555 | "pkg-config", 1556 | "vcpkg", 1557 | ] 1558 | 1559 | [[package]] 1560 | name = "pango" 1561 | version = "0.18.3" 1562 | source = "registry+https://github.com/rust-lang/crates.io-index" 1563 | checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" 1564 | dependencies = [ 1565 | "gio", 1566 | "glib", 1567 | "libc", 1568 | "once_cell", 1569 | "pango-sys", 1570 | ] 1571 | 1572 | [[package]] 1573 | name = "pango-sys" 1574 | version = "0.18.0" 1575 | source = "registry+https://github.com/rust-lang/crates.io-index" 1576 | checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" 1577 | dependencies = [ 1578 | "glib-sys", 1579 | "gobject-sys", 1580 | "libc", 1581 | "system-deps", 1582 | ] 1583 | 1584 | [[package]] 1585 | name = "parking_lot" 1586 | version = "0.12.3" 1587 | source = "registry+https://github.com/rust-lang/crates.io-index" 1588 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 1589 | dependencies = [ 1590 | "lock_api", 1591 | "parking_lot_core", 1592 | ] 1593 | 1594 | [[package]] 1595 | name = "parking_lot_core" 1596 | version = "0.9.10" 1597 | source = "registry+https://github.com/rust-lang/crates.io-index" 1598 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 1599 | dependencies = [ 1600 | "cfg-if", 1601 | "libc", 1602 | "redox_syscall", 1603 | "smallvec", 1604 | "windows-targets", 1605 | ] 1606 | 1607 | [[package]] 1608 | name = "path-slash" 1609 | version = "0.2.1" 1610 | source = "registry+https://github.com/rust-lang/crates.io-index" 1611 | checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42" 1612 | 1613 | [[package]] 1614 | name = "pathdiff" 1615 | version = "0.2.2" 1616 | source = "registry+https://github.com/rust-lang/crates.io-index" 1617 | checksum = "d61c5ce1153ab5b689d0c074c4e7fc613e942dfb7dd9eea5ab202d2ad91fe361" 1618 | 1619 | [[package]] 1620 | name = "percent-encoding" 1621 | version = "2.3.1" 1622 | source = "registry+https://github.com/rust-lang/crates.io-index" 1623 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 1624 | 1625 | [[package]] 1626 | name = "pin-project-lite" 1627 | version = "0.2.15" 1628 | source = "registry+https://github.com/rust-lang/crates.io-index" 1629 | checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" 1630 | 1631 | [[package]] 1632 | name = "pin-utils" 1633 | version = "0.1.0" 1634 | source = "registry+https://github.com/rust-lang/crates.io-index" 1635 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1636 | 1637 | [[package]] 1638 | name = "pkg-config" 1639 | version = "0.3.31" 1640 | source = "registry+https://github.com/rust-lang/crates.io-index" 1641 | checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" 1642 | 1643 | [[package]] 1644 | name = "plotters" 1645 | version = "0.3.7" 1646 | source = "registry+https://github.com/rust-lang/crates.io-index" 1647 | checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" 1648 | dependencies = [ 1649 | "num-traits", 1650 | "plotters-backend", 1651 | "wasm-bindgen", 1652 | "web-sys", 1653 | ] 1654 | 1655 | [[package]] 1656 | name = "plotters-backend" 1657 | version = "0.3.7" 1658 | source = "registry+https://github.com/rust-lang/crates.io-index" 1659 | checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" 1660 | 1661 | [[package]] 1662 | name = "portable-atomic" 1663 | version = "1.9.0" 1664 | source = "registry+https://github.com/rust-lang/crates.io-index" 1665 | checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" 1666 | 1667 | [[package]] 1668 | name = "proc-macro-crate" 1669 | version = "0.1.5" 1670 | source = "registry+https://github.com/rust-lang/crates.io-index" 1671 | checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" 1672 | dependencies = [ 1673 | "toml 0.5.11", 1674 | ] 1675 | 1676 | [[package]] 1677 | name = "proc-macro-crate" 1678 | version = "1.3.1" 1679 | source = "registry+https://github.com/rust-lang/crates.io-index" 1680 | checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" 1681 | dependencies = [ 1682 | "once_cell", 1683 | "toml_edit 0.19.15", 1684 | ] 1685 | 1686 | [[package]] 1687 | name = "proc-macro-crate" 1688 | version = "2.0.2" 1689 | source = "registry+https://github.com/rust-lang/crates.io-index" 1690 | checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" 1691 | dependencies = [ 1692 | "toml_datetime", 1693 | "toml_edit 0.20.2", 1694 | ] 1695 | 1696 | [[package]] 1697 | name = "proc-macro-error" 1698 | version = "1.0.4" 1699 | source = "registry+https://github.com/rust-lang/crates.io-index" 1700 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 1701 | dependencies = [ 1702 | "proc-macro-error-attr", 1703 | "proc-macro2", 1704 | "quote", 1705 | "syn 1.0.109", 1706 | "version_check", 1707 | ] 1708 | 1709 | [[package]] 1710 | name = "proc-macro-error-attr" 1711 | version = "1.0.4" 1712 | source = "registry+https://github.com/rust-lang/crates.io-index" 1713 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 1714 | dependencies = [ 1715 | "proc-macro2", 1716 | "quote", 1717 | "version_check", 1718 | ] 1719 | 1720 | [[package]] 1721 | name = "proc-macro2" 1722 | version = "1.0.89" 1723 | source = "registry+https://github.com/rust-lang/crates.io-index" 1724 | checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" 1725 | dependencies = [ 1726 | "unicode-ident", 1727 | ] 1728 | 1729 | [[package]] 1730 | name = "quote" 1731 | version = "1.0.37" 1732 | source = "registry+https://github.com/rust-lang/crates.io-index" 1733 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 1734 | dependencies = [ 1735 | "proc-macro2", 1736 | ] 1737 | 1738 | [[package]] 1739 | name = "redox_syscall" 1740 | version = "0.5.7" 1741 | source = "registry+https://github.com/rust-lang/crates.io-index" 1742 | checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" 1743 | dependencies = [ 1744 | "bitflags 2.6.0", 1745 | ] 1746 | 1747 | [[package]] 1748 | name = "regex" 1749 | version = "1.11.1" 1750 | source = "registry+https://github.com/rust-lang/crates.io-index" 1751 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 1752 | dependencies = [ 1753 | "aho-corasick", 1754 | "memchr", 1755 | "regex-automata", 1756 | "regex-syntax", 1757 | ] 1758 | 1759 | [[package]] 1760 | name = "regex-automata" 1761 | version = "0.4.8" 1762 | source = "registry+https://github.com/rust-lang/crates.io-index" 1763 | checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" 1764 | dependencies = [ 1765 | "aho-corasick", 1766 | "memchr", 1767 | "regex-syntax", 1768 | ] 1769 | 1770 | [[package]] 1771 | name = "regex-syntax" 1772 | version = "0.8.5" 1773 | source = "registry+https://github.com/rust-lang/crates.io-index" 1774 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 1775 | 1776 | [[package]] 1777 | name = "reqwest" 1778 | version = "0.12.9" 1779 | source = "registry+https://github.com/rust-lang/crates.io-index" 1780 | checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" 1781 | dependencies = [ 1782 | "base64", 1783 | "bytes", 1784 | "encoding_rs", 1785 | "futures-channel", 1786 | "futures-core", 1787 | "futures-util", 1788 | "h2", 1789 | "http", 1790 | "http-body", 1791 | "http-body-util", 1792 | "hyper", 1793 | "hyper-rustls", 1794 | "hyper-tls", 1795 | "hyper-util", 1796 | "ipnet", 1797 | "js-sys", 1798 | "log", 1799 | "mime", 1800 | "native-tls", 1801 | "once_cell", 1802 | "percent-encoding", 1803 | "pin-project-lite", 1804 | "rustls-pemfile", 1805 | "serde", 1806 | "serde_json", 1807 | "serde_urlencoded", 1808 | "sync_wrapper", 1809 | "system-configuration", 1810 | "tokio", 1811 | "tokio-native-tls", 1812 | "tower-service", 1813 | "url", 1814 | "wasm-bindgen", 1815 | "wasm-bindgen-futures", 1816 | "web-sys", 1817 | "windows-registry", 1818 | ] 1819 | 1820 | [[package]] 1821 | name = "ring" 1822 | version = "0.17.8" 1823 | source = "registry+https://github.com/rust-lang/crates.io-index" 1824 | checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" 1825 | dependencies = [ 1826 | "cc", 1827 | "cfg-if", 1828 | "getrandom", 1829 | "libc", 1830 | "spin", 1831 | "untrusted", 1832 | "windows-sys 0.52.0", 1833 | ] 1834 | 1835 | [[package]] 1836 | name = "rustc-demangle" 1837 | version = "0.1.24" 1838 | source = "registry+https://github.com/rust-lang/crates.io-index" 1839 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 1840 | 1841 | [[package]] 1842 | name = "rustc_version" 1843 | version = "0.4.1" 1844 | source = "registry+https://github.com/rust-lang/crates.io-index" 1845 | checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" 1846 | dependencies = [ 1847 | "semver", 1848 | ] 1849 | 1850 | [[package]] 1851 | name = "rustix" 1852 | version = "0.38.39" 1853 | source = "registry+https://github.com/rust-lang/crates.io-index" 1854 | checksum = "375116bee2be9ed569afe2154ea6a99dfdffd257f533f187498c2a8f5feaf4ee" 1855 | dependencies = [ 1856 | "bitflags 2.6.0", 1857 | "errno", 1858 | "libc", 1859 | "linux-raw-sys", 1860 | "windows-sys 0.52.0", 1861 | ] 1862 | 1863 | [[package]] 1864 | name = "rustls" 1865 | version = "0.23.16" 1866 | source = "registry+https://github.com/rust-lang/crates.io-index" 1867 | checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e" 1868 | dependencies = [ 1869 | "once_cell", 1870 | "rustls-pki-types", 1871 | "rustls-webpki", 1872 | "subtle", 1873 | "zeroize", 1874 | ] 1875 | 1876 | [[package]] 1877 | name = "rustls-pemfile" 1878 | version = "2.2.0" 1879 | source = "registry+https://github.com/rust-lang/crates.io-index" 1880 | checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" 1881 | dependencies = [ 1882 | "rustls-pki-types", 1883 | ] 1884 | 1885 | [[package]] 1886 | name = "rustls-pki-types" 1887 | version = "1.10.0" 1888 | source = "registry+https://github.com/rust-lang/crates.io-index" 1889 | checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" 1890 | 1891 | [[package]] 1892 | name = "rustls-webpki" 1893 | version = "0.102.8" 1894 | source = "registry+https://github.com/rust-lang/crates.io-index" 1895 | checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" 1896 | dependencies = [ 1897 | "ring", 1898 | "rustls-pki-types", 1899 | "untrusted", 1900 | ] 1901 | 1902 | [[package]] 1903 | name = "ryu" 1904 | version = "1.0.18" 1905 | source = "registry+https://github.com/rust-lang/crates.io-index" 1906 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 1907 | 1908 | [[package]] 1909 | name = "same-file" 1910 | version = "1.0.6" 1911 | source = "registry+https://github.com/rust-lang/crates.io-index" 1912 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 1913 | dependencies = [ 1914 | "winapi-util", 1915 | ] 1916 | 1917 | [[package]] 1918 | name = "schannel" 1919 | version = "0.1.26" 1920 | source = "registry+https://github.com/rust-lang/crates.io-index" 1921 | checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" 1922 | dependencies = [ 1923 | "windows-sys 0.59.0", 1924 | ] 1925 | 1926 | [[package]] 1927 | name = "scopeguard" 1928 | version = "1.2.0" 1929 | source = "registry+https://github.com/rust-lang/crates.io-index" 1930 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1931 | 1932 | [[package]] 1933 | name = "security-framework" 1934 | version = "2.11.1" 1935 | source = "registry+https://github.com/rust-lang/crates.io-index" 1936 | checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" 1937 | dependencies = [ 1938 | "bitflags 2.6.0", 1939 | "core-foundation", 1940 | "core-foundation-sys", 1941 | "libc", 1942 | "security-framework-sys", 1943 | ] 1944 | 1945 | [[package]] 1946 | name = "security-framework-sys" 1947 | version = "2.12.1" 1948 | source = "registry+https://github.com/rust-lang/crates.io-index" 1949 | checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" 1950 | dependencies = [ 1951 | "core-foundation-sys", 1952 | "libc", 1953 | ] 1954 | 1955 | [[package]] 1956 | name = "semver" 1957 | version = "1.0.23" 1958 | source = "registry+https://github.com/rust-lang/crates.io-index" 1959 | checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" 1960 | 1961 | [[package]] 1962 | name = "serde" 1963 | version = "1.0.214" 1964 | source = "registry+https://github.com/rust-lang/crates.io-index" 1965 | checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" 1966 | dependencies = [ 1967 | "serde_derive", 1968 | ] 1969 | 1970 | [[package]] 1971 | name = "serde_derive" 1972 | version = "1.0.214" 1973 | source = "registry+https://github.com/rust-lang/crates.io-index" 1974 | checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" 1975 | dependencies = [ 1976 | "proc-macro2", 1977 | "quote", 1978 | "syn 2.0.87", 1979 | ] 1980 | 1981 | [[package]] 1982 | name = "serde_json" 1983 | version = "1.0.132" 1984 | source = "registry+https://github.com/rust-lang/crates.io-index" 1985 | checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" 1986 | dependencies = [ 1987 | "itoa", 1988 | "memchr", 1989 | "ryu", 1990 | "serde", 1991 | ] 1992 | 1993 | [[package]] 1994 | name = "serde_spanned" 1995 | version = "0.6.8" 1996 | source = "registry+https://github.com/rust-lang/crates.io-index" 1997 | checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" 1998 | dependencies = [ 1999 | "serde", 2000 | ] 2001 | 2002 | [[package]] 2003 | name = "serde_urlencoded" 2004 | version = "0.7.1" 2005 | source = "registry+https://github.com/rust-lang/crates.io-index" 2006 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 2007 | dependencies = [ 2008 | "form_urlencoded", 2009 | "itoa", 2010 | "ryu", 2011 | "serde", 2012 | ] 2013 | 2014 | [[package]] 2015 | name = "sha2" 2016 | version = "0.10.8" 2017 | source = "registry+https://github.com/rust-lang/crates.io-index" 2018 | checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 2019 | dependencies = [ 2020 | "cfg-if", 2021 | "cpufeatures", 2022 | "digest", 2023 | ] 2024 | 2025 | [[package]] 2026 | name = "sha256" 2027 | version = "1.5.0" 2028 | source = "registry+https://github.com/rust-lang/crates.io-index" 2029 | checksum = "18278f6a914fa3070aa316493f7d2ddfb9ac86ebc06fa3b83bffda487e9065b0" 2030 | dependencies = [ 2031 | "async-trait", 2032 | "bytes", 2033 | "hex", 2034 | "sha2", 2035 | "tokio", 2036 | ] 2037 | 2038 | [[package]] 2039 | name = "shlex" 2040 | version = "1.3.0" 2041 | source = "registry+https://github.com/rust-lang/crates.io-index" 2042 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 2043 | 2044 | [[package]] 2045 | name = "signal-hook" 2046 | version = "0.3.17" 2047 | source = "registry+https://github.com/rust-lang/crates.io-index" 2048 | checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" 2049 | dependencies = [ 2050 | "libc", 2051 | "signal-hook-registry", 2052 | ] 2053 | 2054 | [[package]] 2055 | name = "signal-hook-mio" 2056 | version = "0.2.4" 2057 | source = "registry+https://github.com/rust-lang/crates.io-index" 2058 | checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" 2059 | dependencies = [ 2060 | "libc", 2061 | "mio", 2062 | "signal-hook", 2063 | ] 2064 | 2065 | [[package]] 2066 | name = "signal-hook-registry" 2067 | version = "1.4.2" 2068 | source = "registry+https://github.com/rust-lang/crates.io-index" 2069 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 2070 | dependencies = [ 2071 | "libc", 2072 | ] 2073 | 2074 | [[package]] 2075 | name = "slab" 2076 | version = "0.4.9" 2077 | source = "registry+https://github.com/rust-lang/crates.io-index" 2078 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 2079 | dependencies = [ 2080 | "autocfg", 2081 | ] 2082 | 2083 | [[package]] 2084 | name = "smallvec" 2085 | version = "1.13.2" 2086 | source = "registry+https://github.com/rust-lang/crates.io-index" 2087 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 2088 | 2089 | [[package]] 2090 | name = "snailquote" 2091 | version = "0.3.1" 2092 | source = "registry+https://github.com/rust-lang/crates.io-index" 2093 | checksum = "ec62a949bda7f15800481a711909f946e1204f2460f89210eaf7f57730f88f86" 2094 | dependencies = [ 2095 | "thiserror", 2096 | "unicode_categories", 2097 | ] 2098 | 2099 | [[package]] 2100 | name = "socket2" 2101 | version = "0.5.7" 2102 | source = "registry+https://github.com/rust-lang/crates.io-index" 2103 | checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" 2104 | dependencies = [ 2105 | "libc", 2106 | "windows-sys 0.52.0", 2107 | ] 2108 | 2109 | [[package]] 2110 | name = "spin" 2111 | version = "0.9.8" 2112 | source = "registry+https://github.com/rust-lang/crates.io-index" 2113 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 2114 | 2115 | [[package]] 2116 | name = "stable_deref_trait" 2117 | version = "1.2.0" 2118 | source = "registry+https://github.com/rust-lang/crates.io-index" 2119 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 2120 | 2121 | [[package]] 2122 | name = "stretch" 2123 | version = "0.3.2" 2124 | source = "registry+https://github.com/rust-lang/crates.io-index" 2125 | checksum = "7b0dc6d20ce137f302edf90f9cd3d278866fd7fb139efca6f246161222ad6d87" 2126 | dependencies = [ 2127 | "lazy_static", 2128 | "libm", 2129 | ] 2130 | 2131 | [[package]] 2132 | name = "strsim" 2133 | version = "0.11.1" 2134 | source = "registry+https://github.com/rust-lang/crates.io-index" 2135 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 2136 | 2137 | [[package]] 2138 | name = "subtle" 2139 | version = "2.6.1" 2140 | source = "registry+https://github.com/rust-lang/crates.io-index" 2141 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 2142 | 2143 | [[package]] 2144 | name = "syn" 2145 | version = "1.0.109" 2146 | source = "registry+https://github.com/rust-lang/crates.io-index" 2147 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 2148 | dependencies = [ 2149 | "proc-macro2", 2150 | "quote", 2151 | "unicode-ident", 2152 | ] 2153 | 2154 | [[package]] 2155 | name = "syn" 2156 | version = "2.0.87" 2157 | source = "registry+https://github.com/rust-lang/crates.io-index" 2158 | checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" 2159 | dependencies = [ 2160 | "proc-macro2", 2161 | "quote", 2162 | "unicode-ident", 2163 | ] 2164 | 2165 | [[package]] 2166 | name = "sync_wrapper" 2167 | version = "1.0.1" 2168 | source = "registry+https://github.com/rust-lang/crates.io-index" 2169 | checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" 2170 | dependencies = [ 2171 | "futures-core", 2172 | ] 2173 | 2174 | [[package]] 2175 | name = "synstructure" 2176 | version = "0.13.1" 2177 | source = "registry+https://github.com/rust-lang/crates.io-index" 2178 | checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" 2179 | dependencies = [ 2180 | "proc-macro2", 2181 | "quote", 2182 | "syn 2.0.87", 2183 | ] 2184 | 2185 | [[package]] 2186 | name = "system-configuration" 2187 | version = "0.6.1" 2188 | source = "registry+https://github.com/rust-lang/crates.io-index" 2189 | checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" 2190 | dependencies = [ 2191 | "bitflags 2.6.0", 2192 | "core-foundation", 2193 | "system-configuration-sys", 2194 | ] 2195 | 2196 | [[package]] 2197 | name = "system-configuration-sys" 2198 | version = "0.6.0" 2199 | source = "registry+https://github.com/rust-lang/crates.io-index" 2200 | checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" 2201 | dependencies = [ 2202 | "core-foundation-sys", 2203 | "libc", 2204 | ] 2205 | 2206 | [[package]] 2207 | name = "system-deps" 2208 | version = "6.2.2" 2209 | source = "registry+https://github.com/rust-lang/crates.io-index" 2210 | checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" 2211 | dependencies = [ 2212 | "cfg-expr", 2213 | "heck 0.5.0", 2214 | "pkg-config", 2215 | "toml 0.8.2", 2216 | "version-compare", 2217 | ] 2218 | 2219 | [[package]] 2220 | name = "tar" 2221 | version = "0.4.43" 2222 | source = "registry+https://github.com/rust-lang/crates.io-index" 2223 | checksum = "c65998313f8e17d0d553d28f91a0df93e4dbbbf770279c7bc21ca0f09ea1a1f6" 2224 | dependencies = [ 2225 | "filetime", 2226 | "libc", 2227 | "xattr", 2228 | ] 2229 | 2230 | [[package]] 2231 | name = "target-lexicon" 2232 | version = "0.12.16" 2233 | source = "registry+https://github.com/rust-lang/crates.io-index" 2234 | checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" 2235 | 2236 | [[package]] 2237 | name = "tempfile" 2238 | version = "3.14.0" 2239 | source = "registry+https://github.com/rust-lang/crates.io-index" 2240 | checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" 2241 | dependencies = [ 2242 | "cfg-if", 2243 | "fastrand", 2244 | "once_cell", 2245 | "rustix", 2246 | "windows-sys 0.59.0", 2247 | ] 2248 | 2249 | [[package]] 2250 | name = "thiserror" 2251 | version = "1.0.69" 2252 | source = "registry+https://github.com/rust-lang/crates.io-index" 2253 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 2254 | dependencies = [ 2255 | "thiserror-impl", 2256 | ] 2257 | 2258 | [[package]] 2259 | name = "thiserror-impl" 2260 | version = "1.0.69" 2261 | source = "registry+https://github.com/rust-lang/crates.io-index" 2262 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 2263 | dependencies = [ 2264 | "proc-macro2", 2265 | "quote", 2266 | "syn 2.0.87", 2267 | ] 2268 | 2269 | [[package]] 2270 | name = "tinystr" 2271 | version = "0.7.6" 2272 | source = "registry+https://github.com/rust-lang/crates.io-index" 2273 | checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" 2274 | dependencies = [ 2275 | "displaydoc", 2276 | "zerovec", 2277 | ] 2278 | 2279 | [[package]] 2280 | name = "tokio" 2281 | version = "1.41.1" 2282 | source = "registry+https://github.com/rust-lang/crates.io-index" 2283 | checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" 2284 | dependencies = [ 2285 | "backtrace", 2286 | "bytes", 2287 | "libc", 2288 | "mio", 2289 | "pin-project-lite", 2290 | "socket2", 2291 | "windows-sys 0.52.0", 2292 | ] 2293 | 2294 | [[package]] 2295 | name = "tokio-native-tls" 2296 | version = "0.3.1" 2297 | source = "registry+https://github.com/rust-lang/crates.io-index" 2298 | checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" 2299 | dependencies = [ 2300 | "native-tls", 2301 | "tokio", 2302 | ] 2303 | 2304 | [[package]] 2305 | name = "tokio-rustls" 2306 | version = "0.26.0" 2307 | source = "registry+https://github.com/rust-lang/crates.io-index" 2308 | checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" 2309 | dependencies = [ 2310 | "rustls", 2311 | "rustls-pki-types", 2312 | "tokio", 2313 | ] 2314 | 2315 | [[package]] 2316 | name = "tokio-util" 2317 | version = "0.7.12" 2318 | source = "registry+https://github.com/rust-lang/crates.io-index" 2319 | checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" 2320 | dependencies = [ 2321 | "bytes", 2322 | "futures-core", 2323 | "futures-sink", 2324 | "pin-project-lite", 2325 | "tokio", 2326 | ] 2327 | 2328 | [[package]] 2329 | name = "toml" 2330 | version = "0.5.11" 2331 | source = "registry+https://github.com/rust-lang/crates.io-index" 2332 | checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" 2333 | dependencies = [ 2334 | "serde", 2335 | ] 2336 | 2337 | [[package]] 2338 | name = "toml" 2339 | version = "0.8.2" 2340 | source = "registry+https://github.com/rust-lang/crates.io-index" 2341 | checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" 2342 | dependencies = [ 2343 | "serde", 2344 | "serde_spanned", 2345 | "toml_datetime", 2346 | "toml_edit 0.20.2", 2347 | ] 2348 | 2349 | [[package]] 2350 | name = "toml_datetime" 2351 | version = "0.6.3" 2352 | source = "registry+https://github.com/rust-lang/crates.io-index" 2353 | checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" 2354 | dependencies = [ 2355 | "serde", 2356 | ] 2357 | 2358 | [[package]] 2359 | name = "toml_edit" 2360 | version = "0.19.15" 2361 | source = "registry+https://github.com/rust-lang/crates.io-index" 2362 | checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" 2363 | dependencies = [ 2364 | "indexmap", 2365 | "toml_datetime", 2366 | "winnow", 2367 | ] 2368 | 2369 | [[package]] 2370 | name = "toml_edit" 2371 | version = "0.20.2" 2372 | source = "registry+https://github.com/rust-lang/crates.io-index" 2373 | checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" 2374 | dependencies = [ 2375 | "indexmap", 2376 | "serde", 2377 | "serde_spanned", 2378 | "toml_datetime", 2379 | "winnow", 2380 | ] 2381 | 2382 | [[package]] 2383 | name = "tower-service" 2384 | version = "0.3.3" 2385 | source = "registry+https://github.com/rust-lang/crates.io-index" 2386 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 2387 | 2388 | [[package]] 2389 | name = "tracing" 2390 | version = "0.1.40" 2391 | source = "registry+https://github.com/rust-lang/crates.io-index" 2392 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 2393 | dependencies = [ 2394 | "pin-project-lite", 2395 | "tracing-core", 2396 | ] 2397 | 2398 | [[package]] 2399 | name = "tracing-core" 2400 | version = "0.1.32" 2401 | source = "registry+https://github.com/rust-lang/crates.io-index" 2402 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 2403 | dependencies = [ 2404 | "once_cell", 2405 | ] 2406 | 2407 | [[package]] 2408 | name = "try-lock" 2409 | version = "0.2.5" 2410 | source = "registry+https://github.com/rust-lang/crates.io-index" 2411 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 2412 | 2413 | [[package]] 2414 | name = "typenum" 2415 | version = "1.17.0" 2416 | source = "registry+https://github.com/rust-lang/crates.io-index" 2417 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 2418 | 2419 | [[package]] 2420 | name = "unicode-ident" 2421 | version = "1.0.13" 2422 | source = "registry+https://github.com/rust-lang/crates.io-index" 2423 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 2424 | 2425 | [[package]] 2426 | name = "unicode-segmentation" 2427 | version = "1.12.0" 2428 | source = "registry+https://github.com/rust-lang/crates.io-index" 2429 | checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 2430 | 2431 | [[package]] 2432 | name = "unicode-width" 2433 | version = "0.1.14" 2434 | source = "registry+https://github.com/rust-lang/crates.io-index" 2435 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 2436 | 2437 | [[package]] 2438 | name = "unicode_categories" 2439 | version = "0.1.1" 2440 | source = "registry+https://github.com/rust-lang/crates.io-index" 2441 | checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" 2442 | 2443 | [[package]] 2444 | name = "untrusted" 2445 | version = "0.9.0" 2446 | source = "registry+https://github.com/rust-lang/crates.io-index" 2447 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 2448 | 2449 | [[package]] 2450 | name = "url" 2451 | version = "2.5.3" 2452 | source = "registry+https://github.com/rust-lang/crates.io-index" 2453 | checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" 2454 | dependencies = [ 2455 | "form_urlencoded", 2456 | "idna", 2457 | "percent-encoding", 2458 | ] 2459 | 2460 | [[package]] 2461 | name = "utf16_iter" 2462 | version = "1.0.5" 2463 | source = "registry+https://github.com/rust-lang/crates.io-index" 2464 | checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" 2465 | 2466 | [[package]] 2467 | name = "utf8_iter" 2468 | version = "1.0.4" 2469 | source = "registry+https://github.com/rust-lang/crates.io-index" 2470 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 2471 | 2472 | [[package]] 2473 | name = "utf8parse" 2474 | version = "0.2.2" 2475 | source = "registry+https://github.com/rust-lang/crates.io-index" 2476 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 2477 | 2478 | [[package]] 2479 | name = "vcpkg" 2480 | version = "0.2.15" 2481 | source = "registry+https://github.com/rust-lang/crates.io-index" 2482 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 2483 | 2484 | [[package]] 2485 | name = "version-compare" 2486 | version = "0.2.0" 2487 | source = "registry+https://github.com/rust-lang/crates.io-index" 2488 | checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" 2489 | 2490 | [[package]] 2491 | name = "version_check" 2492 | version = "0.9.5" 2493 | source = "registry+https://github.com/rust-lang/crates.io-index" 2494 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 2495 | 2496 | [[package]] 2497 | name = "walkdir" 2498 | version = "2.5.0" 2499 | source = "registry+https://github.com/rust-lang/crates.io-index" 2500 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 2501 | dependencies = [ 2502 | "same-file", 2503 | "winapi-util", 2504 | ] 2505 | 2506 | [[package]] 2507 | name = "want" 2508 | version = "0.3.1" 2509 | source = "registry+https://github.com/rust-lang/crates.io-index" 2510 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 2511 | dependencies = [ 2512 | "try-lock", 2513 | ] 2514 | 2515 | [[package]] 2516 | name = "wasi" 2517 | version = "0.11.0+wasi-snapshot-preview1" 2518 | source = "registry+https://github.com/rust-lang/crates.io-index" 2519 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 2520 | 2521 | [[package]] 2522 | name = "wasm-bindgen" 2523 | version = "0.2.95" 2524 | source = "registry+https://github.com/rust-lang/crates.io-index" 2525 | checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" 2526 | dependencies = [ 2527 | "cfg-if", 2528 | "once_cell", 2529 | "wasm-bindgen-macro", 2530 | ] 2531 | 2532 | [[package]] 2533 | name = "wasm-bindgen-backend" 2534 | version = "0.2.95" 2535 | source = "registry+https://github.com/rust-lang/crates.io-index" 2536 | checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" 2537 | dependencies = [ 2538 | "bumpalo", 2539 | "log", 2540 | "once_cell", 2541 | "proc-macro2", 2542 | "quote", 2543 | "syn 2.0.87", 2544 | "wasm-bindgen-shared", 2545 | ] 2546 | 2547 | [[package]] 2548 | name = "wasm-bindgen-futures" 2549 | version = "0.4.45" 2550 | source = "registry+https://github.com/rust-lang/crates.io-index" 2551 | checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" 2552 | dependencies = [ 2553 | "cfg-if", 2554 | "js-sys", 2555 | "wasm-bindgen", 2556 | "web-sys", 2557 | ] 2558 | 2559 | [[package]] 2560 | name = "wasm-bindgen-macro" 2561 | version = "0.2.95" 2562 | source = "registry+https://github.com/rust-lang/crates.io-index" 2563 | checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" 2564 | dependencies = [ 2565 | "quote", 2566 | "wasm-bindgen-macro-support", 2567 | ] 2568 | 2569 | [[package]] 2570 | name = "wasm-bindgen-macro-support" 2571 | version = "0.2.95" 2572 | source = "registry+https://github.com/rust-lang/crates.io-index" 2573 | checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" 2574 | dependencies = [ 2575 | "proc-macro2", 2576 | "quote", 2577 | "syn 2.0.87", 2578 | "wasm-bindgen-backend", 2579 | "wasm-bindgen-shared", 2580 | ] 2581 | 2582 | [[package]] 2583 | name = "wasm-bindgen-shared" 2584 | version = "0.2.95" 2585 | source = "registry+https://github.com/rust-lang/crates.io-index" 2586 | checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" 2587 | 2588 | [[package]] 2589 | name = "web-sys" 2590 | version = "0.3.72" 2591 | source = "registry+https://github.com/rust-lang/crates.io-index" 2592 | checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" 2593 | dependencies = [ 2594 | "js-sys", 2595 | "wasm-bindgen", 2596 | ] 2597 | 2598 | [[package]] 2599 | name = "winapi" 2600 | version = "0.3.9" 2601 | source = "registry+https://github.com/rust-lang/crates.io-index" 2602 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 2603 | dependencies = [ 2604 | "winapi-i686-pc-windows-gnu", 2605 | "winapi-x86_64-pc-windows-gnu", 2606 | ] 2607 | 2608 | [[package]] 2609 | name = "winapi-build" 2610 | version = "0.1.1" 2611 | source = "registry+https://github.com/rust-lang/crates.io-index" 2612 | checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 2613 | 2614 | [[package]] 2615 | name = "winapi-i686-pc-windows-gnu" 2616 | version = "0.4.0" 2617 | source = "registry+https://github.com/rust-lang/crates.io-index" 2618 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 2619 | 2620 | [[package]] 2621 | name = "winapi-util" 2622 | version = "0.1.9" 2623 | source = "registry+https://github.com/rust-lang/crates.io-index" 2624 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 2625 | dependencies = [ 2626 | "windows-sys 0.59.0", 2627 | ] 2628 | 2629 | [[package]] 2630 | name = "winapi-x86_64-pc-windows-gnu" 2631 | version = "0.4.0" 2632 | source = "registry+https://github.com/rust-lang/crates.io-index" 2633 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 2634 | 2635 | [[package]] 2636 | name = "windows-registry" 2637 | version = "0.2.0" 2638 | source = "registry+https://github.com/rust-lang/crates.io-index" 2639 | checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" 2640 | dependencies = [ 2641 | "windows-result", 2642 | "windows-strings", 2643 | "windows-targets", 2644 | ] 2645 | 2646 | [[package]] 2647 | name = "windows-result" 2648 | version = "0.2.0" 2649 | source = "registry+https://github.com/rust-lang/crates.io-index" 2650 | checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" 2651 | dependencies = [ 2652 | "windows-targets", 2653 | ] 2654 | 2655 | [[package]] 2656 | name = "windows-strings" 2657 | version = "0.1.0" 2658 | source = "registry+https://github.com/rust-lang/crates.io-index" 2659 | checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" 2660 | dependencies = [ 2661 | "windows-result", 2662 | "windows-targets", 2663 | ] 2664 | 2665 | [[package]] 2666 | name = "windows-sys" 2667 | version = "0.52.0" 2668 | source = "registry+https://github.com/rust-lang/crates.io-index" 2669 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 2670 | dependencies = [ 2671 | "windows-targets", 2672 | ] 2673 | 2674 | [[package]] 2675 | name = "windows-sys" 2676 | version = "0.59.0" 2677 | source = "registry+https://github.com/rust-lang/crates.io-index" 2678 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 2679 | dependencies = [ 2680 | "windows-targets", 2681 | ] 2682 | 2683 | [[package]] 2684 | name = "windows-targets" 2685 | version = "0.52.6" 2686 | source = "registry+https://github.com/rust-lang/crates.io-index" 2687 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 2688 | dependencies = [ 2689 | "windows_aarch64_gnullvm", 2690 | "windows_aarch64_msvc", 2691 | "windows_i686_gnu", 2692 | "windows_i686_gnullvm", 2693 | "windows_i686_msvc", 2694 | "windows_x86_64_gnu", 2695 | "windows_x86_64_gnullvm", 2696 | "windows_x86_64_msvc", 2697 | ] 2698 | 2699 | [[package]] 2700 | name = "windows_aarch64_gnullvm" 2701 | version = "0.52.6" 2702 | source = "registry+https://github.com/rust-lang/crates.io-index" 2703 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 2704 | 2705 | [[package]] 2706 | name = "windows_aarch64_msvc" 2707 | version = "0.52.6" 2708 | source = "registry+https://github.com/rust-lang/crates.io-index" 2709 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 2710 | 2711 | [[package]] 2712 | name = "windows_i686_gnu" 2713 | version = "0.52.6" 2714 | source = "registry+https://github.com/rust-lang/crates.io-index" 2715 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 2716 | 2717 | [[package]] 2718 | name = "windows_i686_gnullvm" 2719 | version = "0.52.6" 2720 | source = "registry+https://github.com/rust-lang/crates.io-index" 2721 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 2722 | 2723 | [[package]] 2724 | name = "windows_i686_msvc" 2725 | version = "0.52.6" 2726 | source = "registry+https://github.com/rust-lang/crates.io-index" 2727 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 2728 | 2729 | [[package]] 2730 | name = "windows_x86_64_gnu" 2731 | version = "0.52.6" 2732 | source = "registry+https://github.com/rust-lang/crates.io-index" 2733 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 2734 | 2735 | [[package]] 2736 | name = "windows_x86_64_gnullvm" 2737 | version = "0.52.6" 2738 | source = "registry+https://github.com/rust-lang/crates.io-index" 2739 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 2740 | 2741 | [[package]] 2742 | name = "windows_x86_64_msvc" 2743 | version = "0.52.6" 2744 | source = "registry+https://github.com/rust-lang/crates.io-index" 2745 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 2746 | 2747 | [[package]] 2748 | name = "winnow" 2749 | version = "0.5.40" 2750 | source = "registry+https://github.com/rust-lang/crates.io-index" 2751 | checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" 2752 | dependencies = [ 2753 | "memchr", 2754 | ] 2755 | 2756 | [[package]] 2757 | name = "write16" 2758 | version = "1.0.0" 2759 | source = "registry+https://github.com/rust-lang/crates.io-index" 2760 | checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" 2761 | 2762 | [[package]] 2763 | name = "writeable" 2764 | version = "0.5.5" 2765 | source = "registry+https://github.com/rust-lang/crates.io-index" 2766 | checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" 2767 | 2768 | [[package]] 2769 | name = "xattr" 2770 | version = "1.3.1" 2771 | source = "registry+https://github.com/rust-lang/crates.io-index" 2772 | checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" 2773 | dependencies = [ 2774 | "libc", 2775 | "linux-raw-sys", 2776 | "rustix", 2777 | ] 2778 | 2779 | [[package]] 2780 | name = "yoke" 2781 | version = "0.7.4" 2782 | source = "registry+https://github.com/rust-lang/crates.io-index" 2783 | checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" 2784 | dependencies = [ 2785 | "serde", 2786 | "stable_deref_trait", 2787 | "yoke-derive", 2788 | "zerofrom", 2789 | ] 2790 | 2791 | [[package]] 2792 | name = "yoke-derive" 2793 | version = "0.7.4" 2794 | source = "registry+https://github.com/rust-lang/crates.io-index" 2795 | checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" 2796 | dependencies = [ 2797 | "proc-macro2", 2798 | "quote", 2799 | "syn 2.0.87", 2800 | "synstructure", 2801 | ] 2802 | 2803 | [[package]] 2804 | name = "zerofrom" 2805 | version = "0.1.4" 2806 | source = "registry+https://github.com/rust-lang/crates.io-index" 2807 | checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" 2808 | dependencies = [ 2809 | "zerofrom-derive", 2810 | ] 2811 | 2812 | [[package]] 2813 | name = "zerofrom-derive" 2814 | version = "0.1.4" 2815 | source = "registry+https://github.com/rust-lang/crates.io-index" 2816 | checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" 2817 | dependencies = [ 2818 | "proc-macro2", 2819 | "quote", 2820 | "syn 2.0.87", 2821 | "synstructure", 2822 | ] 2823 | 2824 | [[package]] 2825 | name = "zeroize" 2826 | version = "1.8.1" 2827 | source = "registry+https://github.com/rust-lang/crates.io-index" 2828 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 2829 | 2830 | [[package]] 2831 | name = "zerovec" 2832 | version = "0.10.4" 2833 | source = "registry+https://github.com/rust-lang/crates.io-index" 2834 | checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" 2835 | dependencies = [ 2836 | "yoke", 2837 | "zerofrom", 2838 | "zerovec-derive", 2839 | ] 2840 | 2841 | [[package]] 2842 | name = "zerovec-derive" 2843 | version = "0.10.3" 2844 | source = "registry+https://github.com/rust-lang/crates.io-index" 2845 | checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" 2846 | dependencies = [ 2847 | "proc-macro2", 2848 | "quote", 2849 | "syn 2.0.87", 2850 | ] 2851 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "common/device", 5 | "common/connection", 6 | "ares-package", 7 | "ares-install", 8 | "ares-push", 9 | "ares-launch", 10 | "ares-device", 11 | "ares-shell", 12 | ] 13 | 14 | [workspace.dependencies] 15 | clap = { version = "4.4.6", features = ["derive", "env"] } 16 | serde = { version = "1.0.214", features = ["derive"] } 17 | serde_json = "1.0.132" 18 | libssh-rs = "0.3.3" 19 | libssh-rs-sys = { version = "0.2.4", default-features = false } 20 | sha256 = "1.5.0" 21 | regex = "1.11.1" 22 | indicatif = "0.17.8" 23 | reqwest = "0.12.9" 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ares-cli-rs 2 | 3 | Rust rewrite of [@webosose/ares-cli](https://github.com/webosose/ares-cli). 4 | 5 | This tool focuses on reducing dependencies, and performance. 6 | Key features and options will be added from time to time. -------------------------------------------------------------------------------- /ares-device/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ares-device" 3 | version = "0.1.2" 4 | edition = "2021" 5 | authors = ["Mariotaku Lee "] 6 | license = "Apache-2.0" 7 | description = "Tools for getting device information" 8 | 9 | [dependencies] 10 | common-device = { path = "../common/device" } 11 | common-connection = { path = "../common/connection" } 12 | clap = { workspace = true } 13 | cfg-if = "1.0.0" 14 | 15 | [target.'cfg(target_os="windows")'.dependencies] 16 | native-windows-gui = { version = "1.0.13" } 17 | native-windows-derive = "1.0.5" 18 | libssh-rs-sys = { workspace = true, features = ["vendored-openssl", "libz-sys"] } 19 | 20 | [target.'cfg(not(target_os="windows"))'.dependencies] 21 | libssh-rs-sys = { workspace = true, default-features = false } 22 | 23 | [target.'cfg(all(not(target_os="windows"), not(target_os="macos")))'.dependencies] 24 | gtk = "0.18.1" 25 | 26 | [build-dependencies] 27 | embed-manifest = "1.4.0" 28 | 29 | [package.metadata.deb] 30 | section = "devel" 31 | -------------------------------------------------------------------------------- /ares-device/ares-device.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /ares-device/build.rs: -------------------------------------------------------------------------------- 1 | use embed_manifest::{embed_manifest, new_manifest}; 2 | 3 | fn main() { 4 | if std::env::var_os("CARGO_CFG_WINDOWS").is_some() { 5 | embed_manifest(new_manifest("ares-device.manifest")) 6 | .expect("unable to embed manifest file"); 7 | } 8 | println!("cargo:rerun-if-changed=build.rs"); 9 | } 10 | -------------------------------------------------------------------------------- /ares-device/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::process::exit; 2 | 3 | use clap::Parser; 4 | 5 | mod picker; 6 | 7 | use ares_device_lib::DeviceManager; 8 | use picker::{DeviceSelection, PickDevice}; 9 | 10 | #[derive(Parser, Debug)] 11 | #[command(about)] 12 | struct Cli { 13 | #[arg( 14 | short, 15 | long, 16 | default_missing_value = "", 17 | num_args = 0..2, 18 | value_name = "DEVICE", 19 | env = "ARES_DEVICE", 20 | help = "Specify DEVICE to use, show picker if no value specified" 21 | )] 22 | device: Option, 23 | } 24 | 25 | fn main() { 26 | let cli = Cli::parse(); 27 | let manager = DeviceManager::default(); 28 | let device = if let Some(d) = manager.pick(cli.device.as_ref()).unwrap() { 29 | d 30 | } else { 31 | eprintln!("Device not found"); 32 | exit(1); 33 | }; 34 | println!("{}", device.name); 35 | } 36 | -------------------------------------------------------------------------------- /ares-device/src/picker/gtk/mod.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, Mutex}; 2 | 3 | use ares_device_lib::Device; 4 | use gtk::prelude::*; 5 | use gtk::{Align, Application, ApplicationWindow, Button, Label, ListBox, ListBoxRow, Orientation}; 6 | 7 | use crate::picker::PickPrompt; 8 | 9 | #[derive(Default)] 10 | pub struct PickPromptGtk {} 11 | 12 | impl PickPrompt for PickPromptGtk { 13 | fn pick>(&self, devices: Vec) -> Option { 14 | let app = Application::new(Some("com.ares.devicePickPrompt"), Default::default()); 15 | let items: Vec = devices.iter().map(|d| d.as_ref().clone()).collect(); 16 | let result_index: Arc> = Arc::new(Mutex::new(-1)); 17 | let ui_result = result_index.clone(); 18 | app.connect_activate(move |app| { 19 | let window = Arc::new(ApplicationWindow::new(app)); 20 | let ui_selected: Arc> = Arc::new(Mutex::new(-1)); 21 | 22 | window.set_title("Select Device"); 23 | window.set_border_width(10); 24 | window.set_position(gtk::WindowPosition::Center); 25 | window.set_default_size(400, 300); 26 | window.set_resizable(false); 27 | window.set_keep_above(true); 28 | 29 | let content = gtk::Box::new(Orientation::Vertical, 5); 30 | 31 | let list = ListBox::new(); 32 | let mut selected_row: i32 = -1; 33 | for (index, item) in items.iter().enumerate() { 34 | let row = ListBoxRow::new(); 35 | let label = Label::new(Some(&item.name)); 36 | label.set_halign(Align::Start); 37 | row.set_child(Some(&label)); 38 | list.insert(&row, -1); 39 | if item.default.unwrap_or(false) { 40 | selected_row = index as i32; 41 | } 42 | } 43 | list.select_row(list.row_at_index(selected_row).as_ref()); 44 | list.set_vexpand(true); 45 | list.set_activate_on_single_click(false); 46 | let index = ui_selected.clone(); 47 | list.connect_row_selected(move |_, selected| { 48 | *index.lock().unwrap() = selected.map(|row| row.index()).unwrap_or(-1); 49 | }); 50 | 51 | { 52 | let window = window.clone(); 53 | let ui_result = ui_result.clone(); 54 | list.connect_row_activated(move |_, selected| { 55 | *ui_result.lock().unwrap() = selected.index(); 56 | window.close(); 57 | }); 58 | } 59 | 60 | content.add(&list); 61 | 62 | let buttons = gtk::Box::new(Orientation::Horizontal, 5); 63 | let ok = Button::with_label("OK"); 64 | let cancel = Button::with_label("Cancel"); 65 | 66 | buttons.add(&ok); 67 | buttons.add(&cancel); 68 | buttons.set_valign(Align::End); 69 | buttons.set_halign(Align::End); 70 | 71 | { 72 | let window = window.clone(); 73 | let ui_selected = ui_selected.clone(); 74 | let ui_result = ui_result.clone(); 75 | ok.connect_clicked(move |_| { 76 | *ui_result.lock().unwrap() = *ui_selected.lock().unwrap(); 77 | window.close(); 78 | }); 79 | } 80 | 81 | { 82 | let window = window.clone(); 83 | cancel.connect_clicked(move |_| { 84 | window.close(); 85 | }); 86 | } 87 | 88 | content.add(&buttons); 89 | 90 | window.add(&content); 91 | 92 | window.show_all(); 93 | }); 94 | app.run_with_args::<&str>(&[]); 95 | 96 | let index = result_index.lock().unwrap().clone(); 97 | if index < 0 { 98 | return None; 99 | } 100 | return devices.get(index as usize).map(|v| v.as_ref().clone()); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /ares-device/src/picker/mod.rs: -------------------------------------------------------------------------------- 1 | use std::io::Error; 2 | use std::str::FromStr; 3 | 4 | use ares_device_lib::{Device, DeviceManager}; 5 | cfg_if::cfg_if! { 6 | if #[cfg(target_os="windows")] { 7 | mod windows; 8 | } else if #[cfg(target_os = "macos")] { 9 | } else { 10 | mod gtk; 11 | } 12 | } 13 | 14 | pub trait PickDevice { 15 | fn pick(&self, selection: Option<&DeviceSelection>) -> Result, Error>; 16 | } 17 | 18 | #[derive(Clone, Debug)] 19 | pub enum DeviceSelection { 20 | Name(String), 21 | Pick, 22 | } 23 | 24 | trait PickPrompt: Default { 25 | fn pick>(&self, devices: Vec) -> Option; 26 | } 27 | 28 | impl PickDevice for DeviceManager { 29 | fn pick(&self, selection: Option<&DeviceSelection>) -> Result, Error> { 30 | let devices = self.list()?; 31 | let device = match selection { 32 | Some(DeviceSelection::Name(s)) => devices.iter().find(|d| &d.name == s).cloned(), 33 | Some(DeviceSelection::Pick) => { 34 | cfg_if::cfg_if! { 35 | if #[cfg(target_os="windows")] { 36 | windows::PickPromptWindows::default().pick(devices) 37 | } else if #[cfg(target_os = "macos")] { 38 | None 39 | } else { 40 | gtk::PickPromptGtk::default().pick(devices) 41 | } 42 | } 43 | } 44 | None => devices.iter().find(|d| d.default.unwrap_or(false)).cloned(), 45 | }; 46 | Ok(device) 47 | } 48 | } 49 | 50 | impl FromStr for DeviceSelection { 51 | type Err = Error; 52 | 53 | fn from_str(s: &str) -> Result { 54 | if s.is_empty() { 55 | Ok(Self::Pick) 56 | } else { 57 | Ok(Self::Name(s.to_string())) 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ares-device/src/picker/windows/mod.rs: -------------------------------------------------------------------------------- 1 | extern crate native_windows_derive as nwd; 2 | extern crate native_windows_gui as nwg; 3 | 4 | use std::fmt::{Display, Formatter}; 5 | use std::sync::Mutex; 6 | 7 | use nwd::NwgUi; 8 | use nwg::NativeUi; 9 | 10 | use ares_device_lib::Device; 11 | 12 | use crate::picker::PickPrompt; 13 | 14 | #[derive(Default)] 15 | pub struct PickPromptWindows {} 16 | 17 | impl PickPrompt for PickPromptWindows { 18 | fn pick>(&self, devices: Vec) -> Option { 19 | nwg::init().expect("Failed to init Native Windows GUI"); 20 | nwg::Font::set_global_family("Segoe UI").expect("Failed to set default font"); 21 | 22 | let app = PickPromptApp::default(); 23 | *app.index.lock().unwrap() = -1; 24 | let ui = PickPromptApp::build_ui(app).expect("Failed to build UI"); 25 | ui.devices.set_collection( 26 | devices 27 | .iter() 28 | .map(|d| DeviceEntry { 29 | device: Some(d.as_ref().clone()), 30 | }) 31 | .collect(), 32 | ); 33 | ui.devices.set_selection( 34 | devices 35 | .iter() 36 | .position(|d| d.as_ref().default.unwrap_or(false)), 37 | ); 38 | ui.on_selection_change(); 39 | 40 | nwg::dispatch_thread_events(); 41 | 42 | let index = ui.index.lock().unwrap().clone() as usize; 43 | devices.get(index).map(|d| d.as_ref()).cloned() 44 | } 45 | } 46 | 47 | #[derive(Default, NwgUi)] 48 | pub struct PickPromptApp { 49 | #[nwg_control(size: (400, 500), center: true, topmost:true, title: "Select Device", flags: "WINDOW|VISIBLE" 50 | )] 51 | #[nwg_events( OnWindowClose: [PickPromptApp::on_close])] 52 | window: nwg::Window, 53 | 54 | #[nwg_control(size: (380, 420), position: (10, 10))] 55 | #[nwg_events( OnListBoxSelect: [PickPromptApp::on_selection_change], OnListBoxDoubleClick: [PickPromptApp::on_confirm] 56 | )] 57 | devices: nwg::ListBox, 58 | 59 | #[nwg_control(text: "Select", size: (185, 60), position: (10, 420), enabled: false)] 60 | #[nwg_events( OnButtonClick: [PickPromptApp::on_confirm])] 61 | ok: nwg::Button, 62 | 63 | #[nwg_control(text: "Cancel", size: (185, 60), position: (205, 420))] 64 | #[nwg_events( OnButtonClick: [PickPromptApp::on_cancel])] 65 | cancel: nwg::Button, 66 | 67 | index: Mutex, 68 | } 69 | 70 | #[derive(Default)] 71 | struct DeviceEntry { 72 | device: Option, 73 | } 74 | 75 | impl Display for DeviceEntry { 76 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 77 | if let Some(device) = &self.device { 78 | f.write_str(&device.name) 79 | } else { 80 | f.write_str("") 81 | } 82 | } 83 | } 84 | 85 | impl PickPromptApp { 86 | fn on_close(&self) { 87 | nwg::stop_thread_dispatch(); 88 | } 89 | 90 | fn on_confirm(&self) { 91 | self.window.close(); 92 | } 93 | 94 | fn on_cancel(&self) { 95 | *self.index.lock().unwrap() = -1; 96 | self.window.close(); 97 | } 98 | 99 | fn on_selection_change(&self) { 100 | if let Some(index) = self.devices.selection() { 101 | *self.index.lock().unwrap() = index as i32; 102 | self.ok.set_enabled(true); 103 | } else { 104 | *self.index.lock().unwrap() = -1; 105 | self.ok.set_enabled(false); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /ares-install/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ares-install" 3 | version = "0.1.5" 4 | edition = "2021" 5 | authors = ["Mariotaku Lee "] 6 | license = "Apache-2.0" 7 | description = "Install or Remove app from a device" 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | common-device = { path = "../common/device" } 13 | common-connection = { path = "../common/connection" } 14 | clap = { workspace = true } 15 | serde = { workspace = true, features = ["derive"] } 16 | serde_json = { workspace = true } 17 | libssh-rs = { workspace = true } 18 | sha256 = { workspace = true } 19 | regex = { workspace = true } 20 | indicatif = { workspace = true } 21 | 22 | [package.metadata.deb] 23 | section = "devel" -------------------------------------------------------------------------------- /ares-install/src/install.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::{Error as IoError, ErrorKind}; 3 | use std::path::Path; 4 | use std::time::Duration; 5 | 6 | use indicatif::{ProgressBar, ProgressStyle}; 7 | use regex::Regex; 8 | use serde::{Deserialize, Serialize}; 9 | use serde_json::Error as JsonError; 10 | 11 | use ares_connection_lib::luna::{Luna, LunaError, Message}; 12 | use ares_connection_lib::session::DeviceSession; 13 | use ares_connection_lib::transfer::{FileTransfer, TransferError}; 14 | 15 | pub(crate) trait InstallApp { 16 | fn install_app>(&self, package: P) -> Result<(), InstallError>; 17 | } 18 | 19 | #[derive(Debug)] 20 | pub enum InstallError { 21 | Response { error_code: i32, reason: String }, 22 | Luna(LunaError), 23 | Transfer(TransferError), 24 | Io(IoError), 25 | } 26 | 27 | #[derive(Serialize, Debug)] 28 | #[serde(rename_all = "camelCase")] 29 | struct InstallPayload { 30 | id: String, 31 | ipk_url: String, 32 | subscribe: bool, 33 | } 34 | 35 | #[derive(Deserialize, Debug)] 36 | struct InstallResponse { 37 | details: Option, 38 | } 39 | 40 | #[derive(Deserialize, Debug)] 41 | #[serde(rename_all = "camelCase")] 42 | struct InstallResponseDetails { 43 | package_id: Option, 44 | state: Option, 45 | error_code: Option, 46 | reason: Option, 47 | } 48 | 49 | impl InstallApp for DeviceSession { 50 | fn install_app>(&self, package: P) -> Result<(), InstallError> { 51 | let mut file = File::open(&package)?; 52 | let file_size = file.metadata()?.len(); 53 | let checksum = sha256::try_digest(package.as_ref()).map_err(|e| { 54 | IoError::new( 55 | ErrorKind::Other, 56 | format!( 57 | "Failed to generate checksum for {}: {:?}", 58 | package.as_ref().to_string_lossy(), 59 | e 60 | ), 61 | ) 62 | })?; 63 | let ipk_path = format!("/media/developer/temp/ares_install_{}.ipk", &checksum[..10]); 64 | 65 | let package_display_name = package 66 | .as_ref() 67 | .file_name() 68 | .map(|s| s.to_string_lossy()) 69 | .unwrap_or_else(|| package.as_ref().to_string_lossy()); 70 | 71 | self.mkdir(&mut Path::new("/media/developer/temp"), 0o777)?; 72 | 73 | let pb = ProgressBar::new(file_size); 74 | pb.suspend(|| { 75 | println!( 76 | "Uploading {} to {}...", 77 | package_display_name, self.device.name 78 | ) 79 | }); 80 | pb.enable_steady_tick(Duration::from_millis(50)); 81 | pb.set_prefix("Uploading"); 82 | pb.set_style(ProgressStyle::with_template("{prefix:10.bold.dim} {spinner} {percent:>3}% [{wide_bar}] {bytes}/{total_bytes} {eta} ETA") 83 | .unwrap()); 84 | 85 | self.put(&mut file, &ipk_path, |transferred| { 86 | pb.set_position(transferred as u64); 87 | })?; 88 | 89 | pb.suspend(|| { 90 | println!( 91 | "Installing {} on {}...", 92 | package_display_name, self.device.name 93 | ) 94 | }); 95 | pb.set_prefix("Installing"); 96 | 97 | let spinner_style = 98 | ProgressStyle::with_template("{prefix:10.bold.dim} {spinner} {wide_msg}").unwrap(); 99 | pb.set_style(spinner_style); 100 | 101 | let result = match self.subscribe( 102 | "luna://com.webos.appInstallService/dev/install", 103 | InstallPayload { 104 | id: String::from("com.ares.defaultName"), 105 | ipk_url: ipk_path.clone(), 106 | subscribe: true, 107 | }, 108 | true, 109 | ) { 110 | Ok(subscription) => subscription 111 | .filter_map(|item| { 112 | map_installer_message( 113 | item, 114 | &Regex::new(r"(?i)installed").unwrap(), 115 | |progress| { 116 | pb.set_message( 117 | progress 118 | .strip_prefix("installing : ") 119 | .unwrap_or(&progress) 120 | .to_string(), 121 | ); 122 | }, 123 | ) 124 | }) 125 | .next() 126 | .unwrap_or_else(|| Ok(String::new())), 127 | Err(e) => Err(e.into()), 128 | }; 129 | 130 | if let Ok(package_id) = &result { 131 | pb.suspend(|| println!("Installed package {}!", package_id)); 132 | } 133 | pb.suspend(|| println!("Deleting uploaded package...")); 134 | 135 | pb.set_prefix("Cleanup"); 136 | pb.set_message("Deleting uploaded package"); 137 | 138 | if let Err(e) = self.rm(&ipk_path) { 139 | pb.suspend(|| { 140 | eprintln!("Failed to delete {}: {:?}", ipk_path, e); 141 | }); 142 | } 143 | pb.finish_and_clear(); 144 | 145 | result?; 146 | Ok(()) 147 | } 148 | } 149 | 150 | pub(crate) fn map_installer_message( 151 | item: std::io::Result, 152 | expected: &Regex, 153 | progress: F, 154 | ) -> Option> { 155 | match item { 156 | Ok(message) => match message.deserialize::() { 157 | Ok(resp) => { 158 | if let Some(details) = resp.details { 159 | if let Some(state) = details.state { 160 | if Regex::new(r"(?i)FAILED").unwrap().is_match(&state) { 161 | return Some(Err(InstallError::Response { 162 | error_code: details.error_code.unwrap_or(0), 163 | reason: details.reason.unwrap_or(String::from("unknown error")), 164 | })); 165 | } else if Regex::new(r"(?i)^SUCCESS").unwrap().is_match(&state) 166 | || expected.is_match(&state) 167 | { 168 | return Some(Ok(details.package_id.unwrap_or(String::from("")))); 169 | } else { 170 | progress(state); 171 | } 172 | } 173 | } 174 | None 175 | } 176 | Err(e) => Some(Err(e.into())), 177 | }, 178 | Err(e) => Some(Err(InstallError::Io(e))), 179 | } 180 | } 181 | 182 | impl From for InstallError { 183 | fn from(value: LunaError) -> Self { 184 | Self::Luna(value) 185 | } 186 | } 187 | 188 | impl From for InstallError { 189 | fn from(value: TransferError) -> Self { 190 | Self::Transfer(value) 191 | } 192 | } 193 | 194 | impl From for InstallError { 195 | fn from(value: IoError) -> Self { 196 | Self::Io(value) 197 | } 198 | } 199 | 200 | impl From for InstallError { 201 | fn from(value: JsonError) -> Self { 202 | Self::Io(IoError::new( 203 | ErrorKind::InvalidData, 204 | format!("Invalid JSON data: {value:?}"), 205 | )) 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /ares-install/src/list.rs: -------------------------------------------------------------------------------- 1 | use libssh_rs::Session; 2 | use serde::Deserialize; 3 | 4 | use ares_connection_lib::luna::{Luna, LunaEmptyPayload}; 5 | 6 | pub(crate) trait ListApps { 7 | fn list_apps(&self); 8 | } 9 | 10 | #[derive(Deserialize, Debug)] 11 | #[serde(rename_all = "camelCase")] 12 | pub struct ListAppsResponse { 13 | pub apps: Vec, 14 | pub return_value: bool, 15 | } 16 | 17 | #[derive(Deserialize, Debug)] 18 | #[serde(rename_all = "camelCase")] 19 | pub struct App { 20 | pub id: String, 21 | pub version: String, 22 | pub r#type: String, 23 | pub title: String, 24 | pub vendor: Option, 25 | } 26 | 27 | impl ListApps for Session { 28 | fn list_apps(&self) { 29 | let resp: ListAppsResponse = self 30 | .call( 31 | "luna://com.webos.applicationManager/dev/listApps", 32 | LunaEmptyPayload::default(), 33 | true, 34 | ) 35 | .unwrap(); 36 | for app in resp.apps { 37 | println!("{}", app.id); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /ares-install/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | use std::process::exit; 3 | 4 | use clap::Parser; 5 | 6 | use crate::remove::RemoveApp; 7 | use ares_connection_lib::session::NewSession; 8 | use ares_device_lib::DeviceManager; 9 | use install::InstallApp; 10 | use list::ListApps; 11 | 12 | mod install; 13 | mod list; 14 | mod remove; 15 | 16 | #[derive(Parser, Debug)] 17 | #[command(about)] 18 | struct Cli { 19 | #[arg( 20 | short, 21 | long, 22 | value_name = "DEVICE", 23 | env = "ARES_DEVICE", 24 | help = "Specify DEVICE to use" 25 | )] 26 | device: Option, 27 | #[arg(short, long, group = "action", help = "List the installed apps")] 28 | list: bool, 29 | #[arg( 30 | short, 31 | long, 32 | group = "action", 33 | value_name = "APP_ID", 34 | help = "Remove app with APP_ID" 35 | )] 36 | remove: Option, 37 | #[arg( 38 | value_name = "PACKAGE_FILE", 39 | group = "action", 40 | help = "webOS package with .ipk extension" 41 | )] 42 | package: Option, 43 | } 44 | 45 | fn main() { 46 | let cli = Cli::parse(); 47 | let manager = DeviceManager::default(); 48 | let device = manager.find_or_default(cli.device).unwrap(); 49 | if device.is_none() { 50 | eprintln!("Device not found"); 51 | exit(1); 52 | } 53 | let device = device.unwrap(); 54 | let session = device.new_session().unwrap(); 55 | if cli.list { 56 | session.list_apps(); 57 | } else if let Some(id) = cli.remove { 58 | println!("Removing {id}..."); 59 | match session.remove_app(&id) { 60 | Ok(_) => println!("{id} removed."), 61 | Err(e) => { 62 | eprintln!("Failed to remove {id}: {e:?}"); 63 | exit(1); 64 | } 65 | } 66 | } else if let Some(package) = cli.package { 67 | match session.install_app(package) { 68 | Ok(_) => {} 69 | Err(e) => { 70 | eprintln!("Failed to install: {e:?}"); 71 | exit(1); 72 | } 73 | } 74 | } else { 75 | Cli::parse_from(vec!["", "--help"]); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /ares-install/src/remove.rs: -------------------------------------------------------------------------------- 1 | use regex::Regex; 2 | use serde::Serialize; 3 | 4 | use ares_connection_lib::luna::Luna; 5 | use ares_connection_lib::session::DeviceSession; 6 | 7 | use crate::install::{map_installer_message, InstallError}; 8 | 9 | pub(crate) trait RemoveApp { 10 | fn remove_app(&self, package_id: &str) -> Result; 11 | } 12 | 13 | #[derive(Serialize, Debug)] 14 | #[serde(rename_all = "camelCase")] 15 | struct RemovePayload { 16 | id: String, 17 | subscribe: bool, 18 | } 19 | 20 | impl RemoveApp for DeviceSession { 21 | fn remove_app(&self, package_id: &str) -> Result { 22 | let result = match self.subscribe( 23 | "luna://com.webos.appInstallService/dev/remove", 24 | RemovePayload { 25 | id: String::from(package_id), 26 | subscribe: true, 27 | }, 28 | true, 29 | ) { 30 | Ok(subscription) => subscription 31 | .filter_map(|item| { 32 | map_installer_message(item, &Regex::new(r"(?i)removed").unwrap(), |progress| { 33 | println!("{}", progress); 34 | }) 35 | }) 36 | .next(), 37 | Err(e) => Some(Err(e.into())), 38 | }; 39 | 40 | result.unwrap() 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /ares-launch/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ares-launch" 3 | version = "0.1.2" 4 | edition = "2021" 5 | authors = ["Mariotaku Lee "] 6 | license = "Apache-2.0" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | common-device = { path = "../common/device" } 12 | common-connection = { path = "../common/connection" } 13 | clap = { workspace = true } 14 | serde = { workspace = true, features = ["derive"] } 15 | serde_json = { workspace = true } 16 | libssh-rs = { workspace = true } 17 | 18 | [package.metadata.deb] 19 | section = "devel" -------------------------------------------------------------------------------- /ares-launch/src/close.rs: -------------------------------------------------------------------------------- 1 | use libssh_rs::Session; 2 | use serde_json::Value; 3 | use std::process::exit; 4 | 5 | use ares_connection_lib::luna::Luna; 6 | 7 | use crate::{LaunchParams, LaunchResponse}; 8 | 9 | pub(crate) trait CloseApp { 10 | fn close_app(&self, app_id: String, params: Value); 11 | } 12 | impl CloseApp for Session { 13 | fn close_app(&self, app_id: String, params: Value) { 14 | let response: LaunchResponse = self 15 | .call( 16 | "luna://com.webos.applicationManager/dev/closeByAppId", 17 | &LaunchParams { 18 | id: app_id.clone(), 19 | subscribe: false, 20 | params, 21 | }, 22 | true, 23 | ) 24 | .unwrap(); 25 | if response.return_value { 26 | println!("Closed application {app_id}"); 27 | } else { 28 | eprintln!( 29 | "Failed to close {app_id}: {} ({})", 30 | response.error_text.unwrap_or(String::from("unknown error")), 31 | response.error_code.unwrap_or(-1) 32 | ); 33 | exit(1); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /ares-launch/src/launch.rs: -------------------------------------------------------------------------------- 1 | use libssh_rs::Session; 2 | use serde_json::Value; 3 | use std::process::exit; 4 | 5 | use ares_connection_lib::luna::Luna; 6 | 7 | use crate::{LaunchParams, LaunchResponse}; 8 | 9 | pub(crate) trait LaunchApp { 10 | fn launch_app(&self, app_id: String, params: Value); 11 | } 12 | 13 | impl LaunchApp for Session { 14 | fn launch_app(&self, app_id: String, params: Value) { 15 | let response: LaunchResponse = self 16 | .call( 17 | "luna://com.webos.applicationManager/launch", 18 | &LaunchParams { 19 | id: app_id.clone(), 20 | subscribe: false, 21 | params, 22 | }, 23 | true, 24 | ) 25 | .unwrap(); 26 | if response.return_value { 27 | println!("Launched application {app_id}"); 28 | } else { 29 | eprintln!( 30 | "Failed to launch {app_id}: {} ({})", 31 | response.error_text.unwrap_or(String::from("unknown error")), 32 | response.error_code.unwrap_or(-1) 33 | ); 34 | exit(1); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /ares-launch/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::process::exit; 2 | 3 | use clap::Parser; 4 | use serde::{Deserialize, Serialize}; 5 | use serde_json::{json, Map, Value}; 6 | 7 | use ares_connection_lib::session::NewSession; 8 | use ares_device_lib::DeviceManager; 9 | 10 | use crate::close::CloseApp; 11 | use crate::launch::LaunchApp; 12 | use crate::running::ListRunning; 13 | 14 | mod close; 15 | mod launch; 16 | mod running; 17 | 18 | #[derive(Parser, Debug)] 19 | #[command(about)] 20 | struct Cli { 21 | #[arg( 22 | short, 23 | long, 24 | value_name = "DEVICE", 25 | env = "ARES_DEVICE", 26 | help = "Specify DEVICE to use" 27 | )] 28 | device: Option, 29 | #[arg(short, long, group = "action", help = "Close a running app")] 30 | close: bool, 31 | #[arg(short, long, group = "action", help = "List running apps")] 32 | running: bool, 33 | #[arg( 34 | short, 35 | long, 36 | value_name = "PARAMS", 37 | help = "Launch/Close an app with the specified parameters" 38 | )] 39 | params: Vec, 40 | #[arg(value_name = "APP_ID", help = "An app id described in appinfo.json")] 41 | app_id: Option, 42 | } 43 | 44 | #[derive(Serialize, Debug)] 45 | struct LaunchParams { 46 | id: String, 47 | subscribe: bool, 48 | #[serde(skip_serializing_if = "Value::is_null")] 49 | params: Value, 50 | } 51 | 52 | #[derive(Deserialize, Debug)] 53 | #[serde(rename_all = "camelCase")] 54 | struct LaunchResponse { 55 | return_value: bool, 56 | error_code: Option, 57 | error_text: Option, 58 | } 59 | 60 | fn main() { 61 | let cli = Cli::parse(); 62 | let manager = DeviceManager::default(); 63 | let device = manager.find_or_default(cli.device).unwrap(); 64 | if device.is_none() { 65 | eprintln!("Device not found"); 66 | exit(1); 67 | } 68 | let device = device.unwrap(); 69 | let session = device.new_session().unwrap(); 70 | 71 | if cli.running { 72 | session.list_running(); 73 | return; 74 | } 75 | if cli.app_id.is_none() { 76 | Cli::parse_from(vec!["", "--help"]); 77 | return; 78 | } 79 | 80 | let mut params: Value = Value::Null; 81 | if !cli.params.is_empty() { 82 | let mut map = Map::new(); 83 | for p in cli.params { 84 | if p.starts_with("{") { 85 | match serde_json::from_str::(&p) { 86 | Ok(mut value) => map.append(&mut value.as_object_mut().unwrap()), 87 | Err(e) => eprintln!("Ignoring param `{p}` as error occurred parsing it: {e:?}"), 88 | } 89 | } else { 90 | let mut split = p.splitn(2, '='); 91 | if let (Some(left), Some(right)) = (split.next(), split.next()) { 92 | map.insert(String::from(left), json!(right)); 93 | } else { 94 | eprintln!("Ignoring unrecognized param `{p}`") 95 | } 96 | } 97 | } 98 | params = Value::Object(map); 99 | } 100 | if cli.close { 101 | session.close_app(cli.app_id.unwrap(), params); 102 | } else { 103 | session.launch_app(cli.app_id.unwrap(), params); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /ares-launch/src/running.rs: -------------------------------------------------------------------------------- 1 | use std::process::exit; 2 | 3 | use libssh_rs::Session; 4 | use serde::Deserialize; 5 | use serde_json::json; 6 | 7 | use ares_connection_lib::luna::Luna; 8 | 9 | pub(crate) trait ListRunning { 10 | fn list_running(&self); 11 | } 12 | 13 | #[derive(Deserialize, Debug)] 14 | #[serde(rename_all = "camelCase")] 15 | struct ListRunningResponse { 16 | return_value: bool, 17 | error_code: Option, 18 | error_text: Option, 19 | running: Option>, 20 | } 21 | 22 | #[derive(Deserialize, Debug)] 23 | #[serde(rename_all = "camelCase")] 24 | struct RunningProcess { 25 | id: String, 26 | } 27 | 28 | impl ListRunning for Session { 29 | fn list_running(&self) { 30 | let response: ListRunningResponse = self 31 | .call( 32 | "luna://com.webos.applicationManager/dev/running", 33 | json!({"subscribe":false}), 34 | true, 35 | ) 36 | .unwrap(); 37 | if response.return_value { 38 | if let Some(running) = response.running { 39 | for proc in running { 40 | println!("{}", proc.id); 41 | } 42 | } 43 | } else { 44 | eprintln!( 45 | "Failed to list running apps: {} ({})", 46 | response.error_text.unwrap_or(String::from("unknown error")), 47 | response.error_code.unwrap_or(-1) 48 | ); 49 | exit(1); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /ares-package/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ares-package" 3 | version = "0.1.4" 4 | edition = "2021" 5 | authors = ["Mariotaku Lee "] 6 | license = "Apache-2.0" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | clap = { workspace = true } 12 | serde = { workspace = true, features = ["derive"] } 13 | serde_json = { workspace = true } 14 | ar = "0.9.0" 15 | tar = "0.4.43" 16 | flate2 = "1.0.34" 17 | path-slash = "0.2.1" 18 | elf = "0.7.4" 19 | regex = { workspace = true } 20 | walkdir = "2.5.0" 21 | 22 | [package.metadata.deb] 23 | section = "devel" -------------------------------------------------------------------------------- /ares-package/src/input/app.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::needless_return)] 2 | 3 | use std::io::{Error, ErrorKind, Read, Result}; 4 | 5 | use serde::Deserialize; 6 | 7 | use crate::ParseFrom; 8 | 9 | #[derive(Debug, Deserialize)] 10 | pub struct AppInfo { 11 | pub id: String, 12 | pub version: String, 13 | pub r#type: String, 14 | pub main: String, 15 | pub title: String, 16 | pub vendor: Option, 17 | } 18 | 19 | impl ParseFrom for AppInfo { 20 | fn parse_from(reader: R) -> Result { 21 | serde_json::from_reader(reader).map_err(|e| { 22 | Error::new( 23 | ErrorKind::InvalidData, 24 | format!("Invalid appinfo.json: {e:?}"), 25 | ) 26 | }) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ares-package/src/input/data.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | use std::fs::File; 3 | use std::io::{Error, ErrorKind, Result}; 4 | use std::path::{Path, PathBuf}; 5 | 6 | use regex::Regex; 7 | 8 | use crate::input::app::AppInfo; 9 | use crate::input::service::ServiceInfo; 10 | use crate::input::validation::{PackageArch, Validation, ValidationInfo}; 11 | use crate::{PackageInfo, ParseFrom}; 12 | 13 | #[derive(Debug)] 14 | pub struct DataInfo { 15 | pub package: PackageInfo, 16 | pub package_data: Vec, 17 | pub app: ComponentInfo, 18 | pub services: Vec>, 19 | pub excludes: Option, 20 | } 21 | 22 | #[derive(Debug)] 23 | pub struct ComponentInfo { 24 | pub path: PathBuf, 25 | pub info: T, 26 | pub excludes: Option, 27 | } 28 | 29 | impl DataInfo { 30 | pub fn from_input( 31 | app_dir: P1, 32 | service_dirs: &[P2], 33 | excludes: &[E], 34 | ) -> Result 35 | where 36 | P1: AsRef, 37 | P2: AsRef, 38 | E: AsRef, 39 | { 40 | let app_dir = app_dir.as_ref(); 41 | let app_info: AppInfo = AppInfo::parse_from(File::open(app_dir.join("appinfo.json"))?)?; 42 | let mut services: Vec> = Vec::new(); 43 | let mut exclude_queries = Vec::::new(); 44 | for pattern in excludes { 45 | let mut pattern = String::from(pattern.as_ref()); 46 | if pattern.starts_with(".") { 47 | pattern = pattern.replacen(".", "^\\.", 1); 48 | } else if pattern.starts_with("*") { 49 | pattern = pattern.replacen("*", "", 1); 50 | } 51 | pattern.push('$'); 52 | exclude_queries.push(pattern); 53 | } 54 | let mut excludes: Option = None; 55 | if !exclude_queries.is_empty() { 56 | excludes = Some(Regex::new(&format!("(?i){}", exclude_queries.join("|"))).unwrap()); 57 | } 58 | for service_dir in service_dirs { 59 | let service_dir = service_dir.as_ref(); 60 | let service_info = 61 | ServiceInfo::parse_from(File::open(service_dir.join("services.json"))?)?; 62 | services.push(ComponentInfo { 63 | path: service_dir.to_path_buf(), 64 | info: service_info, 65 | excludes: excludes.clone(), 66 | }); 67 | } 68 | let package_info = PackageInfo { 69 | id: app_info.id.clone(), 70 | version: app_info.version.clone(), 71 | app: app_info.id.clone(), 72 | services: services.iter().map(|info| info.info.id.clone()).collect(), 73 | }; 74 | let mut package_info_data = serde_json::to_vec_pretty(&package_info)?; 75 | package_info_data.push(b'\n'); 76 | Ok(DataInfo { 77 | package: package_info, 78 | package_data: package_info_data, 79 | app: ComponentInfo { 80 | path: app_dir.to_path_buf(), 81 | info: app_info, 82 | excludes: excludes.clone(), 83 | }, 84 | services, 85 | excludes, 86 | }) 87 | } 88 | } 89 | 90 | impl Validation for DataInfo { 91 | fn validate(&self) -> Result { 92 | let app_validation = self.app.validate()?; 93 | let mut archs = HashSet::::new(); 94 | let mut size_sum = self.package_data.len() as u64; 95 | if let Some(arch) = &app_validation.arch { 96 | archs.insert(arch.clone()); 97 | } 98 | size_sum += app_validation.size; 99 | 100 | for info in &self.services { 101 | let service_validation = info.validate()?; 102 | if let Some(arch) = &service_validation.arch { 103 | archs.insert(arch.clone()); 104 | } 105 | size_sum += service_validation.size; 106 | } 107 | 108 | if archs.len() > 1 { 109 | return Err(Error::new( 110 | ErrorKind::InvalidData, 111 | "Mixed architecture is not allowed", 112 | )); 113 | } 114 | Ok(ValidationInfo { 115 | arch: archs.iter().next().cloned(), 116 | size: size_sum, 117 | }) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /ares-package/src/input/mod.rs: -------------------------------------------------------------------------------- 1 | use std::io::Result; 2 | use std::path::Path; 3 | 4 | use path_slash::PathExt; 5 | use regex::Regex; 6 | use walkdir::{DirEntry, WalkDir}; 7 | 8 | pub mod app; 9 | pub mod data; 10 | pub mod service; 11 | pub mod validation; 12 | 13 | pub(crate) fn filter_by_excludes>( 14 | base: P, 15 | entry: &DirEntry, 16 | excludes: Option<&Regex>, 17 | ) -> bool { 18 | if let Some(exclude) = excludes { 19 | return !exclude.is_match( 20 | entry 21 | .path() 22 | .strip_prefix(base) 23 | .unwrap() 24 | .to_slash_lossy() 25 | .as_ref(), 26 | ); 27 | } 28 | true 29 | } 30 | 31 | pub(crate) fn dir_size>(path: P, excludes: Option<&Regex>) -> Result { 32 | let walker = WalkDir::new(path.as_ref()); 33 | let mut size = 0; 34 | for entry in walker 35 | .into_iter() 36 | .filter_entry(|entry| filter_by_excludes(&path, entry, excludes)) 37 | { 38 | let entry = entry?; 39 | size += entry.metadata()?.len(); 40 | } 41 | Ok(size) 42 | } 43 | -------------------------------------------------------------------------------- /ares-package/src/input/service.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Error, ErrorKind, Read, Result}; 2 | 3 | use serde::Deserialize; 4 | 5 | use crate::ParseFrom; 6 | 7 | #[derive(Debug, Deserialize)] 8 | pub struct ServiceInfo { 9 | pub id: String, 10 | pub description: Option, 11 | pub engine: Option, 12 | pub executable: Option, 13 | } 14 | 15 | impl ParseFrom for ServiceInfo { 16 | fn parse_from(reader: R) -> Result { 17 | serde_json::from_reader(reader).map_err(|e| { 18 | Error::new( 19 | ErrorKind::InvalidData, 20 | format!("Invalid services.json: {e:?}"), 21 | ) 22 | }) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /ares-package/src/input/validation.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | use std::fs::File; 3 | use std::io::{Error, ErrorKind, Result}; 4 | use std::path::Path; 5 | use std::str::FromStr; 6 | 7 | use elf::endian::AnyEndian; 8 | use elf::to_str::e_machine_to_string; 9 | use elf::ElfStream; 10 | 11 | use crate::input::app::AppInfo; 12 | use crate::input::data::ComponentInfo; 13 | use crate::input::dir_size; 14 | use crate::input::service::ServiceInfo; 15 | 16 | #[derive(Eq, Hash, PartialEq, Debug, Clone)] 17 | pub enum PackageArch { 18 | ARM, 19 | X86(String), 20 | ALL, 21 | } 22 | 23 | pub struct ValidationInfo { 24 | pub arch: Option, 25 | pub size: u64, 26 | } 27 | 28 | pub trait Validation { 29 | fn validate(&self) -> Result; 30 | } 31 | 32 | impl Validation for ComponentInfo { 33 | fn validate(&self) -> Result { 34 | let size = dir_size(&self.path, self.excludes.as_ref())?; 35 | let mut arch: Option = None; 36 | if self.info.r#type == "native" { 37 | arch = infer_arch(self.path.join(&self.info.main))?; 38 | } 39 | Ok(ValidationInfo { arch, size }) 40 | } 41 | } 42 | 43 | impl Validation for ComponentInfo { 44 | fn validate(&self) -> Result { 45 | let size = dir_size(&self.path, self.excludes.as_ref())?; 46 | let mut arch: Option = None; 47 | if let (Some(engine), Some(executable)) = (&self.info.engine, &self.info.executable) { 48 | if engine == "native" { 49 | arch = infer_arch(self.path.join(executable))?; 50 | } 51 | } 52 | Ok(ValidationInfo { arch, size }) 53 | } 54 | } 55 | 56 | impl Display for PackageArch { 57 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 58 | let str = match self { 59 | PackageArch::ARM => String::from("arm"), 60 | PackageArch::ALL => String::from("all"), 61 | PackageArch::X86(s) => s.clone(), 62 | }; 63 | write!(f, "{}", str) 64 | } 65 | } 66 | 67 | impl FromStr for PackageArch { 68 | type Err = String; 69 | 70 | fn from_str(s: &str) -> std::result::Result { 71 | match s { 72 | "arm" => Ok(PackageArch::ARM), 73 | "all" => Ok(PackageArch::ALL), 74 | "i386" | "i486" | "i586" | "i686" | "x86" => Ok(PackageArch::X86(String::from(s))), 75 | _ => Err(format!("Invalid architecture {s}")), 76 | } 77 | } 78 | } 79 | 80 | fn infer_arch>(path: P) -> Result> { 81 | let elf = ElfStream::::open_stream(File::open(path.as_ref())?) 82 | .map_err(|e| Error::new(ErrorKind::InvalidData, format!("Bad binary: {e:?}")))?; 83 | match elf.ehdr.e_machine { 84 | elf::abi::EM_ARM => Ok(Some(PackageArch::ARM)), 85 | elf::abi::EM_386 => Ok(Some(PackageArch::X86(String::from("x86")))), 86 | e => Err(Error::new( 87 | ErrorKind::InvalidData, 88 | format!("Unsupported binary machine type {}", e_machine_to_string(e)), 89 | )), 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /ares-package/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | use std::fs::File; 3 | use std::io::Read; 4 | use std::path::PathBuf; 5 | use std::time::SystemTime; 6 | 7 | use ar::Builder; 8 | use clap::Parser; 9 | use serde::Serialize; 10 | 11 | use crate::input::data::DataInfo; 12 | use crate::input::validation::{PackageArch, Validation}; 13 | use crate::packaging::control::{AppendControl, ControlInfo}; 14 | use crate::packaging::data::AppendData; 15 | use crate::packaging::header::AppendHeader; 16 | 17 | mod input; 18 | mod packaging; 19 | 20 | #[derive(Parser, Debug)] 21 | #[command(about)] 22 | struct Cli { 23 | #[arg( 24 | short, 25 | long, 26 | value_name = "OUTPUT_DIR", 27 | help = "Use OUTPUT_DIR as the output directory" 28 | )] 29 | outdir: Option, 30 | #[arg( 31 | short = 'e', 32 | long, 33 | value_name = "PATTERN", 34 | help = "Exclude files, given as a PATTERN" 35 | )] 36 | app_exclude: Vec, 37 | #[arg( 38 | short = 'A', 39 | long, 40 | value_name = "ARCH", 41 | help = "Explicitly specify the architecture" 42 | )] 43 | force_arch: Option, 44 | #[arg(help = "App directory containing a valid appinfo.json file.")] 45 | app_dir: PathBuf, 46 | #[arg(help = "Directory containing a valid services.json file")] 47 | service_dir: Vec, 48 | } 49 | 50 | #[derive(Debug, Serialize)] 51 | pub struct PackageInfo { 52 | id: String, 53 | version: String, 54 | app: String, 55 | #[serde(skip_serializing_if = "Vec::is_empty")] 56 | services: Vec, 57 | } 58 | 59 | pub trait ParseFrom: Sized { 60 | fn parse_from(reader: R) -> std::io::Result; 61 | } 62 | 63 | fn main() { 64 | let cli = Cli::parse(); 65 | let app_dir = cli.app_dir; 66 | let outdir = cli 67 | .outdir 68 | .or_else(|| std::env::current_dir().ok()) 69 | .expect("Invalid output directory"); 70 | 71 | let data = DataInfo::from_input(&app_dir, &cli.service_dir, &cli.app_exclude).unwrap(); 72 | let package_info = &data.package; 73 | let validation = data.validate().unwrap(); 74 | let arch = cli 75 | .force_arch 76 | .or_else(|| validation.arch.clone()) 77 | .unwrap_or_else(|| PackageArch::ALL); 78 | if let Some(validation_arch) = &validation.arch { 79 | if std::mem::discriminant(&arch) != std::mem::discriminant(validation_arch) { 80 | eprintln!( 81 | "Incompatible architecture: {} != {}", 82 | arch.to_string(), 83 | validation_arch.to_string() 84 | ); 85 | return; 86 | } 87 | } 88 | 89 | let path = outdir.join(format!( 90 | "{}_{}_{}.ipk", 91 | package_info.id, package_info.version, arch.to_string() 92 | )); 93 | println!("Packaging {}...", path.to_string_lossy()); 94 | let ipk_file = File::create(path).unwrap(); 95 | let mut ar = Builder::new(ipk_file); 96 | 97 | let mtime = SystemTime::now() 98 | .duration_since(SystemTime::UNIX_EPOCH) 99 | .unwrap() 100 | .as_secs(); 101 | 102 | ar.append_header(mtime).unwrap(); 103 | let control = ControlInfo { 104 | package: package_info.id.clone(), 105 | version: package_info.version.clone(), 106 | installed_size: validation.size, 107 | architecture: arch.to_string(), 108 | }; 109 | ar.append_control(&control, mtime).unwrap(); 110 | ar.append_data(&data, mtime).unwrap(); 111 | println!("Done."); 112 | } 113 | -------------------------------------------------------------------------------- /ares-package/src/packaging/control.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Display, Formatter}; 2 | use std::io::{Cursor, Write as IoWrite}; 3 | use std::ops::Deref; 4 | 5 | use ar::{Builder as ArBuilder, Header as ArHeader}; 6 | use flate2::write::GzEncoder; 7 | use flate2::Compression; 8 | use tar::{Builder as TarBuilder, Header as TarHeader}; 9 | 10 | pub struct ControlInfo { 11 | pub package: String, 12 | pub version: String, 13 | pub installed_size: u64, 14 | pub architecture: String, 15 | } 16 | 17 | pub trait AppendControl { 18 | fn append_control(&mut self, info: &ControlInfo, mtime: u64) -> std::io::Result<()>; 19 | } 20 | 21 | impl AppendControl for ArBuilder 22 | where 23 | W: IoWrite, 24 | { 25 | fn append_control(&mut self, info: &ControlInfo, mtime: u64) -> std::io::Result<()> { 26 | let control = info.to_string().into_bytes(); 27 | 28 | let mut control_tar_gz = Vec::::new(); 29 | let gz = GzEncoder::new(&mut control_tar_gz, Compression::default()); 30 | let mut tar = TarBuilder::new(gz); 31 | 32 | let mut tar_header = TarHeader::new_gnu(); 33 | tar_header.set_mode(0o100644); 34 | tar_header.set_size(control.len() as u64); 35 | tar_header.set_mtime(mtime); 36 | tar_header.set_cksum(); 37 | tar.append_data(&mut tar_header, "control", control.deref())?; 38 | drop(tar); 39 | 40 | let mut ar_header = ArHeader::new(b"control.tar.gz".to_vec(), control_tar_gz.len() as u64); 41 | ar_header.set_mode(0o100644); 42 | ar_header.set_mtime(mtime); 43 | self.append(&ar_header, Cursor::new(control_tar_gz)) 44 | } 45 | } 46 | 47 | impl Display for ControlInfo { 48 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 49 | f.write_fmt(format_args!("Package: {}\n", self.package))?; 50 | f.write_fmt(format_args!("Version: {}\n", self.version))?; 51 | f.write_fmt(format_args!("Section: {}\n", "misc"))?; 52 | f.write_fmt(format_args!("Priority: {}\n", "optional"))?; 53 | f.write_fmt(format_args!("Architecture: {}\n", self.architecture))?; 54 | f.write_fmt(format_args!("Installed-Size: {}\n", self.installed_size))?; 55 | f.write_fmt(format_args!("Maintainer: {}\n", "N/A "))?; 56 | f.write_fmt(format_args!( 57 | "Description: {}\n", 58 | "This is a webOS application." 59 | ))?; 60 | f.write_fmt(format_args!("webOS-Package-Format-Version: {}\n", 2))?; 61 | f.write_fmt(format_args!("webOS-Packager-Version: {}\n", "x.y.x"))?; 62 | Ok(()) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /ares-package/src/packaging/data.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | use std::fs; 3 | use std::fs::File; 4 | use std::io::{Cursor, Result, Write as IoWrite, Write}; 5 | use std::ops::Deref; 6 | use std::path::{Path, PathBuf}; 7 | 8 | use ar::{Builder as ArBuilder, Header as ArHeader}; 9 | use flate2::write::GzEncoder; 10 | use flate2::Compression; 11 | use path_slash::PathExt as _; 12 | use regex::Regex; 13 | use tar::{Builder as TarBuilder, EntryType, Header as TarHeader}; 14 | use walkdir::WalkDir; 15 | 16 | use crate::input::data::DataInfo; 17 | use crate::input::filter_by_excludes; 18 | use crate::PackageInfo; 19 | 20 | pub trait AppendData { 21 | fn append_data(&mut self, details: &DataInfo, mtime: u64) -> Result<()>; 22 | } 23 | 24 | impl AppendData for ArBuilder 25 | where 26 | W: IoWrite, 27 | { 28 | fn append_data(&mut self, details: &DataInfo, mtime: u64) -> Result<()> { 29 | let info = &details.package; 30 | 31 | let mut data_tar_gz = Vec::::new(); 32 | let gz = GzEncoder::new(&mut data_tar_gz, Compression::default()); 33 | let mut tar = TarBuilder::new(gz); 34 | 35 | let mut dir_entries: HashSet = HashSet::new(); 36 | 37 | append_tree( 38 | &mut tar, 39 | format!("usr/palm/applications/{}/", info.app), 40 | &details.app.path, 41 | &mut dir_entries, 42 | details.excludes.as_ref(), 43 | mtime, 44 | )?; 45 | for service in &details.services { 46 | append_tree( 47 | &mut tar, 48 | format!("usr/palm/services/{}/", service.info.id), 49 | &service.path, 50 | &mut dir_entries, 51 | details.excludes.as_ref(), 52 | mtime, 53 | )?; 54 | } 55 | append_package_info(&mut tar, &mut dir_entries, info, details, mtime)?; 56 | drop(tar); 57 | 58 | let mut ar_header = ArHeader::new(b"data.tar.gz".to_vec(), data_tar_gz.len() as u64); 59 | ar_header.set_mode(0o100644); 60 | ar_header.set_mtime(mtime); 61 | self.append(&ar_header, Cursor::new(data_tar_gz)) 62 | } 63 | } 64 | 65 | fn append_dirs( 66 | tar: &mut TarBuilder, 67 | path: P, 68 | dir_entries: &mut HashSet, 69 | mtime: u64, 70 | ) -> Result<()> 71 | where 72 | W: Write, 73 | P: AsRef, 74 | { 75 | let mut stack = Vec::new(); 76 | let empty = Vec::::new(); 77 | let mut p = path.as_ref(); 78 | while p != Path::new("") { 79 | if dir_entries.contains(p) { 80 | break; 81 | } 82 | stack.insert(0, p); 83 | dir_entries.insert(p.to_path_buf()); 84 | if let Some(parent) = p.parent() { 85 | p = parent; 86 | } 87 | } 88 | for p in stack { 89 | let mut header = TarHeader::new_gnu(); 90 | let mut dir = String::from(p.to_slash_lossy()); 91 | if !dir.ends_with('/') { 92 | dir.push('/'); 93 | } 94 | header.set_entry_type(EntryType::Directory); 95 | header.set_mode(0o100775); 96 | header.set_size(0); 97 | header.set_uid(0); 98 | header.set_gid(5000); 99 | header.set_mtime(mtime); 100 | header.set_cksum(); 101 | println!("Adding {path}", path = dir); 102 | tar.append_data(&mut header, &dir, empty.deref())?; 103 | } 104 | Ok(()) 105 | } 106 | 107 | fn tar_path(prefix: S, path: P) -> PathBuf 108 | where 109 | S: AsRef, 110 | P: AsRef, 111 | { 112 | PathBuf::from(format!( 113 | "{}{}", 114 | prefix.as_ref(), 115 | path.as_ref().to_slash_lossy() 116 | )) 117 | } 118 | 119 | fn append_tree( 120 | tar: &mut TarBuilder, 121 | prefix: S, 122 | path: P, 123 | dir_entries: &mut HashSet, 124 | excludes: Option<&Regex>, 125 | mtime: u64, 126 | ) -> Result<()> 127 | where 128 | W: Write, 129 | S: AsRef, 130 | P: AsRef, 131 | { 132 | let base_path = path.as_ref(); 133 | let walker = WalkDir::new(base_path) 134 | .contents_first(false) 135 | .sort_by_file_name(); 136 | for entry in walker 137 | .into_iter() 138 | .filter_entry(|entry| filter_by_excludes(base_path, entry, excludes)) 139 | { 140 | let entry = entry?; 141 | let entry_type = entry.file_type(); 142 | let entry_metadata = entry.metadata()?; 143 | let entry_path = entry.path(); 144 | let tar_path = tar_path(&prefix, entry_path.strip_prefix(base_path).unwrap()); 145 | if entry_type.is_dir() { 146 | append_dirs(tar, &tar_path, dir_entries, mtime)?; 147 | } else if let Some(parent) = tar_path.parent() { 148 | append_dirs(tar, parent, dir_entries, mtime)?; 149 | } 150 | if entry_type.is_symlink() { 151 | let link_target = fs::read_link(entry_path)?; 152 | let mut header = TarHeader::new_gnu(); 153 | header.set_metadata(&entry_metadata); 154 | header.set_uid(0); 155 | header.set_gid(5000); 156 | header.set_cksum(); 157 | println!( 158 | "Adding {path} -> {target}", 159 | path = tar_path.to_string_lossy(), 160 | target = link_target.to_string_lossy() 161 | ); 162 | tar.append_link(&mut header, tar_path, link_target)?; 163 | } else if entry_type.is_file() { 164 | let mut header = TarHeader::new_gnu(); 165 | header.set_metadata(&entry_metadata); 166 | header.set_uid(0); 167 | header.set_gid(5000); 168 | header.set_cksum(); 169 | println!("Adding {path}", path = tar_path.to_string_lossy()); 170 | tar.append_data(&mut header, tar_path, &mut File::open(entry_path)?)?; 171 | } 172 | } 173 | Ok(()) 174 | } 175 | 176 | fn append_package_info( 177 | tar: &mut TarBuilder, 178 | dir_entries: &mut HashSet, 179 | info: &PackageInfo, 180 | details: &DataInfo, 181 | mtime: u64, 182 | ) -> Result<()> 183 | where 184 | W: Write, 185 | { 186 | let package_dir = format!("usr/palm/packages/{}/", info.id); 187 | append_dirs(tar, &package_dir, dir_entries, mtime)?; 188 | let mut header = TarHeader::new_gnu(); 189 | let pkg_info_path = format!("usr/palm/packages/{}/packageinfo.json", info.id); 190 | header.set_mode(0o100644); 191 | header.set_size(details.package_data.len() as u64); 192 | header.set_mtime(mtime); 193 | header.set_uid(0); 194 | header.set_gid(5000); 195 | header.set_cksum(); 196 | tar.append_data(&mut header, &pkg_info_path, details.package_data.deref())?; 197 | println!("Adding {path}", path = pkg_info_path); 198 | Ok(()) 199 | } 200 | -------------------------------------------------------------------------------- /ares-package/src/packaging/header.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Result, Write as IoWrite}; 2 | use std::ops::Deref; 3 | 4 | use ar::{Builder as ArBuilder, Header}; 5 | 6 | pub trait AppendHeader { 7 | fn append_header(&mut self, mtime: u64) -> Result<()>; 8 | } 9 | 10 | impl AppendHeader for ArBuilder 11 | where 12 | W: IoWrite, 13 | { 14 | fn append_header(&mut self, mtime: u64) -> Result<()> { 15 | let debian_binary = b"2.0\n".to_vec(); 16 | 17 | let mut header = Header::new(b"debian-binary".to_vec(), debian_binary.len() as u64); 18 | header.set_mode(0o100644); 19 | header.set_mtime(mtime); 20 | self.append(&header, debian_binary.deref()) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ares-package/src/packaging/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod control; 2 | pub mod data; 3 | pub mod header; 4 | -------------------------------------------------------------------------------- /ares-push/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ares-push" 3 | version = "0.1.4" 4 | edition = "2021" 5 | authors = ["Mariotaku Lee "] 6 | license = "Apache-2.0" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | common-device = { path = "../common/device" } 12 | common-connection = { path = "../common/connection" } 13 | clap = { workspace = true, features = ["derive", "env"] } 14 | libssh-rs = { workspace = true } 15 | path-slash = "0.2.1" 16 | walkdir = "2.5.0" 17 | 18 | [package.metadata.deb] 19 | section = "devel" -------------------------------------------------------------------------------- /ares-push/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::path::{Path, PathBuf}; 3 | use std::process::exit; 4 | 5 | use clap::Parser; 6 | use path_slash::PathBufExt; 7 | use walkdir::WalkDir; 8 | 9 | use ares_connection_lib::session::NewSession; 10 | use ares_device_lib::DeviceManager; 11 | use libssh_rs::OpenFlags; 12 | 13 | #[derive(Parser, Debug)] 14 | #[command(about)] 15 | struct Cli { 16 | #[arg( 17 | short, 18 | long, 19 | value_name = "DEVICE", 20 | env = "ARES_DEVICE", 21 | help = "Specify DEVICE to use" 22 | )] 23 | device: Option, 24 | #[arg( 25 | value_name = "SOURCE", 26 | help = "Path in the host machine, where files exist.", 27 | required = true 28 | )] 29 | source: Vec, 30 | #[arg( 31 | value_name = "DESTINATION", 32 | help = "Path in the DEVICE, where multiple files can be copied", 33 | required = true 34 | )] 35 | destination: String, 36 | } 37 | 38 | fn main() { 39 | let cli = Cli::parse(); 40 | let manager = DeviceManager::default(); 41 | let Some(device) = manager.find_or_default(cli.device).unwrap() else { 42 | eprintln!("Device not found"); 43 | exit(1); 44 | }; 45 | let session = device.new_session().unwrap(); 46 | let sftp = session.sftp().unwrap(); 47 | for source in cli.source { 48 | let walker = WalkDir::new(&source).contents_first(false); 49 | let dest_base = Path::new(&cli.destination); 50 | let mut source_prefix: &Path = &source; 51 | if cli.destination.ends_with("/") { 52 | if let Some(parent) = source_prefix.parent() { 53 | source_prefix = parent; 54 | } 55 | } 56 | for entry in walker { 57 | match entry { 58 | Ok(entry) => { 59 | let file_type = entry.file_type(); 60 | let dest_path = 61 | dest_base.join(entry.path().strip_prefix(source_prefix).unwrap()); 62 | if file_type.is_dir() { 63 | println!( 64 | "{} => {}", 65 | entry.path().to_string_lossy(), 66 | dest_path.to_slash_lossy() 67 | ); 68 | sftp.create_dir(dest_path.to_slash_lossy().as_ref(), 0o755) 69 | .unwrap_or(()); 70 | } else if file_type.is_file() { 71 | println!( 72 | "{} => {}", 73 | entry.path().to_string_lossy(), 74 | dest_path.to_slash_lossy() 75 | ); 76 | let mut file = match sftp.open( 77 | dest_path.to_slash_lossy().as_ref(), 78 | OpenFlags::WRITE_ONLY | OpenFlags::CREATE | OpenFlags::TRUNCATE, 79 | 0o644, 80 | ) { 81 | Ok(file) => file, 82 | Err(e) => { 83 | eprintln!("Failed to open file: {e:?}"); 84 | continue; 85 | } 86 | }; 87 | let mut loc_file = File::open(entry.path()).unwrap(); 88 | std::io::copy(&mut loc_file, &mut file).unwrap(); 89 | } else if file_type.is_symlink() { 90 | eprintln!("Skipping symlink {}", entry.path().to_string_lossy()); 91 | } 92 | } 93 | Err(e) => eprintln!("Failed to push file: {e:?}"), 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /ares-shell/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ares-shell" 3 | version = "0.1.1" 4 | edition = "2021" 5 | authors = ["Mariotaku Lee "] 6 | license = "Apache-2.0" 7 | 8 | [dependencies] 9 | common-device = { path = "../common/device" } 10 | common-connection = { path = "../common/connection" } 11 | clap = { workspace = true, features = ["derive", "env"] } 12 | libssh-rs = { workspace = true } 13 | crossbeam-channel = "0.5.13" 14 | crossterm = "0.28.1" 15 | -------------------------------------------------------------------------------- /ares-shell/src/dumb.rs: -------------------------------------------------------------------------------- 1 | use std::io::{stdin, Error, Read, Write}; 2 | use std::sync::{Arc, Mutex}; 3 | use std::thread; 4 | use std::thread::JoinHandle; 5 | use std::time::Duration; 6 | 7 | use crossbeam_channel::{select, unbounded, Sender}; 8 | use libssh_rs::Channel; 9 | use libssh_rs::Error::TryAgain; 10 | 11 | pub(crate) fn shell(ch: Channel) -> Result { 12 | let (tx, rx) = unbounded::>(); 13 | let events = EventThread::new(tx); 14 | let mut buf = [0; 1024]; 15 | let mut stderr = false; 16 | loop { 17 | if ch.is_eof() { 18 | break; 19 | } 20 | select! { 21 | recv(rx) -> item => match item { 22 | Ok(data) => { 23 | ch.stdin().write_all(&data[..])?; 24 | ch.stdin().flush()?; 25 | } 26 | Err(_) => { 27 | break; 28 | } 29 | }, 30 | default => { 31 | match ch.read_nonblocking(&mut buf, stderr) { 32 | Err(TryAgain) | Ok(0) => { 33 | if ch.is_closed() { 34 | break; 35 | } 36 | thread::sleep(Duration::from_millis(1)) 37 | } 38 | Ok(size) => { 39 | if stderr { 40 | let mut stderr = std::io::stderr(); 41 | stderr.write_all(&buf[..size])?; 42 | stderr.flush()?; 43 | } else { 44 | let mut stdout = std::io::stdout(); 45 | stdout.write_all(&buf[..size])?; 46 | stdout.flush()?; 47 | } 48 | } 49 | Err(e) => { 50 | return Err(Error::new(std::io::ErrorKind::Other, e.to_string())); 51 | } 52 | } 53 | stderr = !stderr; 54 | } 55 | } 56 | } 57 | drop(events); 58 | Ok(ch.get_exit_status().unwrap_or(-1) as i32) 59 | } 60 | 61 | struct EventThread { 62 | handle: Mutex>>, 63 | terminated: Arc>, 64 | } 65 | 66 | impl EventThread { 67 | fn new(tx: Sender>) -> Self { 68 | let terminated = Arc::new(Mutex::new(false)); 69 | let thread_terminated = Arc::downgrade(&terminated); 70 | Self { 71 | terminated, 72 | handle: Mutex::new(Some(thread::spawn(move || loop { 73 | if let Some(terminated) = thread_terminated.upgrade() { 74 | if *terminated.lock().unwrap() { 75 | break; 76 | } 77 | } else { 78 | break; 79 | } 80 | let mut buf = [0; 1024]; 81 | match stdin().read(&mut buf) { 82 | Ok(size) => { 83 | if !tx.send(buf[..size].to_vec()).is_ok() { 84 | break; 85 | } 86 | } 87 | Err(_) => { 88 | break; 89 | } 90 | } 91 | }))), 92 | } 93 | } 94 | } 95 | 96 | impl Drop for EventThread { 97 | fn drop(&mut self) { 98 | *self.terminated.lock().unwrap() = true; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /ares-shell/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::io::stdout; 2 | use std::process::exit; 3 | 4 | use clap::Parser; 5 | use crossterm::terminal; 6 | use crossterm::tty::IsTty; 7 | 8 | use ares_connection_lib::session::NewSession; 9 | use ares_device_lib::DeviceManager; 10 | 11 | mod dumb; 12 | mod pty; 13 | 14 | #[derive(Parser, Debug)] 15 | #[command(about)] 16 | struct Cli { 17 | #[arg( 18 | short, 19 | long, 20 | value_name = "DEVICE", 21 | env = "ARES_DEVICE", 22 | help = "Specify DEVICE to use" 23 | )] 24 | device: Option, 25 | #[arg(short, long, value_name = "COMMAND", help = "Run COMMAND")] 26 | run: Option, 27 | #[arg(long, group = "pty_opt", help = "Force pseudo-terminal allocation")] 28 | pty: bool, 29 | #[arg(long, group = "pty_opt", help = "Disable pseudo-terminal allocation")] 30 | no_pty: bool, 31 | } 32 | 33 | fn main() { 34 | let cli = Cli::parse(); 35 | let manager = DeviceManager::default(); 36 | let Some(device) = manager.find_or_default(cli.device).unwrap() else { 37 | eprintln!("Device not found"); 38 | exit(255); 39 | }; 40 | 41 | let session = device.new_session().unwrap(); 42 | let ch = session.new_channel().unwrap(); 43 | ch.open_session().unwrap(); 44 | let mut has_pty = false; 45 | if !cli.no_pty && (cli.pty || stdout().is_tty()) { 46 | let (width, height) = terminal::size().unwrap_or((80, 24)); 47 | if let Err(e) = ch.request_pty("xterm", width as u32, height as u32) { 48 | eprintln!("Can't request pty: {:?}", e); 49 | if cli.pty { 50 | exit(255); 51 | } 52 | } else { 53 | has_pty = true; 54 | } 55 | } 56 | if let Some(command) = cli.run { 57 | ch.request_exec(&command).unwrap(); 58 | } else { 59 | ch.request_shell().unwrap(); 60 | } 61 | let result = if has_pty { 62 | pty::shell(ch) 63 | } else { 64 | dumb::shell(ch) 65 | }; 66 | match result { 67 | Ok(code) => exit(code), 68 | Err(e) => { 69 | eprintln!("Error: {:?}", e); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /ares-shell/src/pty.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | use std::sync::{Arc, Mutex}; 3 | use std::thread; 4 | use std::thread::JoinHandle; 5 | use std::time::Duration; 6 | 7 | use crossbeam_channel::{select, unbounded, Sender}; 8 | use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind}; 9 | use crossterm::terminal; 10 | use libssh_rs::Error::TryAgain; 11 | use libssh_rs::{Channel, Error}; 12 | 13 | pub(crate) fn shell(ch: Channel) -> Result { 14 | terminal::enable_raw_mode()?; 15 | let (tx, rx) = unbounded::(); 16 | let events = EventThread::new(tx); 17 | let mut buf = [0; 1024]; 18 | loop { 19 | if ch.is_eof() { 20 | break; 21 | } 22 | select! { 23 | recv(rx) -> ev => match ev { 24 | Ok(Event::Key(key)) => { 25 | if key.kind == KeyEventKind::Release { 26 | continue; 27 | } 28 | send_key(&mut ch.stdin(), &key)?; 29 | } 30 | Ok(Event::Resize(width, height)) => { 31 | ch.change_pty_size(width as u32, height as u32)?; 32 | } 33 | Ok(_) => { 34 | } 35 | Err(_) => { 36 | break; 37 | } 38 | }, 39 | default => { 40 | match ch.read_nonblocking(&mut buf, false) { 41 | Err(TryAgain) | Ok(0) => { 42 | if ch.is_closed() { 43 | break; 44 | } 45 | thread::sleep(Duration::from_millis(1)) 46 | } 47 | Ok(size) => { 48 | let mut stdout = std::io::stdout(); 49 | stdout.write_all(&buf[..size])?; 50 | stdout.flush()?; 51 | } 52 | Err(e) => { 53 | return Err(std::io::Error::new(std::io::ErrorKind::Other, e.to_string())); 54 | } 55 | } 56 | } 57 | } 58 | } 59 | drop(events); 60 | Ok(ch.get_exit_status().unwrap_or(-1) as i32) 61 | } 62 | 63 | fn send_key(stdin: &mut Stdin, key: &KeyEvent) -> Result<(), Error> { 64 | match key.code { 65 | KeyCode::Backspace => { 66 | stdin.write_all(&[0x08, 0x20, 0x08])?; 67 | } 68 | KeyCode::Enter => { 69 | stdin.write_all(&[0x0d])?; 70 | } 71 | KeyCode::Left => { 72 | stdin.write_all(&[0x1b, 0x5b, 0x44])?; 73 | } 74 | KeyCode::Right => { 75 | stdin.write_all(&[0x1b, 0x5b, 0x43])?; 76 | } 77 | KeyCode::Up => { 78 | stdin.write_all(&[0x1b, 0x5b, 0x41])?; 79 | } 80 | KeyCode::Down => { 81 | stdin.write_all(&[0x1b, 0x5b, 0x42])?; 82 | } 83 | KeyCode::Home => { 84 | stdin.write_all(&[0x1b, 0x5b, 0x48])?; 85 | } 86 | KeyCode::End => { 87 | stdin.write_all(&[0x1b, 0x5b, 0x46])?; 88 | } 89 | KeyCode::PageUp => { 90 | stdin.write_all(&[0x1b, 0x5b, 0x35, 0x7e])?; 91 | } 92 | KeyCode::PageDown => { 93 | stdin.write_all(&[0x1b, 0x5b, 0x36, 0x7e])?; 94 | } 95 | KeyCode::Tab => { 96 | stdin.write_all(&[0x09])?; 97 | } 98 | KeyCode::BackTab => { 99 | stdin.write_all(&[0x1b, 0x5b, 0x5a])?; 100 | } 101 | KeyCode::Delete => { 102 | stdin.write_all(&[0x1b, 0x5b, 0x33, 0x7e])?; 103 | } 104 | KeyCode::Insert => { 105 | stdin.write_all(&[0x1b, 0x5b, 0x32, 0x7e])?; 106 | } 107 | KeyCode::F(n) => { 108 | stdin.write_all(&[0x1b, 0x5b, 0x4f, 0x30 + n])?; 109 | } 110 | KeyCode::Char(c) => { 111 | if key 112 | .modifiers 113 | .contains(crossterm::event::KeyModifiers::CONTROL) 114 | { 115 | stdin.write_all(&[c as u8 - 0x61])?; 116 | } else if key.modifiers.contains(crossterm::event::KeyModifiers::ALT) { 117 | stdin.write_all(&[0x1b, c as u8])?; 118 | } else { 119 | stdin.write_all(&[c as u8])?; 120 | } 121 | } 122 | KeyCode::Null => { 123 | stdin.write_all(&[0x0])?; 124 | } 125 | KeyCode::Esc => { 126 | stdin.write_all(&[0x1b])?; 127 | } 128 | _ => {} 129 | } 130 | Ok(()) 131 | } 132 | 133 | struct EventThread { 134 | handle: Mutex>>, 135 | terminated: Arc>, 136 | } 137 | 138 | impl EventThread { 139 | fn new(tx: Sender) -> Self { 140 | let terminated = Arc::new(Mutex::new(false)); 141 | let thread_terminated = Arc::downgrade(&terminated); 142 | Self { 143 | terminated, 144 | handle: Mutex::new(Some(thread::spawn(move || loop { 145 | if let Some(terminated) = thread_terminated.upgrade() { 146 | if *terminated.lock().unwrap() { 147 | break; 148 | } 149 | } else { 150 | break; 151 | } 152 | let Ok(has_event) = crossterm::event::poll(Duration::from_millis(20)) else { 153 | break; 154 | }; 155 | if !has_event { 156 | continue; 157 | } 158 | let Ok(event) = crossterm::event::read() else { 159 | break; 160 | }; 161 | if !tx.send(event).is_ok() { 162 | break; 163 | } 164 | }))), 165 | } 166 | } 167 | } 168 | 169 | impl Drop for EventThread { 170 | fn drop(&mut self) { 171 | terminal::disable_raw_mode().unwrap(); 172 | *self.terminated.lock().unwrap() = true; 173 | if let Some(hnd) = self.handle.lock().unwrap().take() { 174 | hnd.join().unwrap(); 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /common/connection/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "common-connection" 3 | version = "0.1.1" 4 | edition = "2021" 5 | 6 | [lib] 7 | name = "ares_connection_lib" 8 | 9 | [dependencies] 10 | common-device = { path = "../device" } 11 | serde = { workspace = true, features = ["derive"] } 12 | serde_json = { workspace = true } 13 | libssh-rs = { workspace = true } 14 | reqwest = { workspace = true, features = ["blocking"] } 15 | snailquote = "0.3.1" 16 | path-slash = "0.2.1" 17 | 18 | [target.'cfg(target_os="windows")'.dependencies] 19 | libssh-rs-sys = { workspace = true, features = ["vendored-openssl", "libz-sys"] } 20 | 21 | [target.'cfg(not(target_os="windows"))'.dependencies] 22 | libssh-rs-sys = { workspace = true, default-features = false } -------------------------------------------------------------------------------- /common/connection/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::io::Error; 2 | 3 | mod setup; 4 | pub mod session; 5 | pub mod luna; 6 | pub mod transfer; 7 | 8 | pub trait DeviceSetupManager { 9 | fn novacom_getkey(&self, address: &str, passphrase: &str) -> Result; 10 | 11 | fn localkey_verify(&self, name: &str, passphrase: &str) -> Result<(), Error>; 12 | } 13 | -------------------------------------------------------------------------------- /common/connection/src/luna/luna.rs: -------------------------------------------------------------------------------- 1 | use std::io::Read; 2 | use std::io::{Error as IoError, ErrorKind}; 3 | 4 | use libssh_rs::{Error as SshError, Session}; 5 | use serde::de::DeserializeOwned; 6 | use serde::Serialize; 7 | use serde_json::Error as JsonError; 8 | 9 | use crate::luna::{Luna, LunaError, Subscription}; 10 | use crate::session::SessionError; 11 | 12 | impl Luna for Session { 13 | fn call(&self, uri: &str, payload: P, public: bool) -> Result 14 | where 15 | P: Sized + Serialize, 16 | R: DeserializeOwned, 17 | { 18 | let ch = self.new_channel()?; 19 | ch.open_session()?; 20 | let luna_cmd = if public { "luna-send-pub" } else { "luna-send" }; 21 | let uri = snailquote::escape(uri.into()); 22 | let payload_str = serde_json::to_string(&payload)?; 23 | ch.request_exec(&format!( 24 | "{luna_cmd} -n 1 {uri} {}", 25 | snailquote::escape(&payload_str) 26 | ))?; 27 | let mut buf = String::new(); 28 | ch.stdout().read_to_string(&mut buf)?; 29 | let mut stderr = String::new(); 30 | ch.stderr().read_to_string(&mut stderr)?; 31 | let exit_code = ch.get_exit_status().unwrap_or(0); 32 | ch.close()?; 33 | if exit_code == 0 { 34 | return Ok(serde_json::from_str(&buf)?); 35 | } 36 | Err(LunaError::NotAvailable) 37 | } 38 | 39 | fn subscribe

(&self, uri: &str, payload: P, public: bool) -> Result 40 | where 41 | P: Sized + Serialize, 42 | { 43 | let ch = self.new_channel()?; 44 | ch.open_session()?; 45 | let luna_cmd = if public { "luna-send-pub" } else { "luna-send" }; 46 | let uri = snailquote::escape(uri.into()); 47 | let payload_str = serde_json::to_string(&payload)?; 48 | ch.request_exec(&format!( 49 | "{luna_cmd} -i {uri} {}", 50 | snailquote::escape(&payload_str) 51 | ))?; 52 | Ok(Subscription { 53 | ch, 54 | buffer: Vec::new(), 55 | }) 56 | } 57 | } 58 | 59 | impl From for LunaError { 60 | fn from(value: SshError) -> Self { 61 | Self::Session(value.into()) 62 | } 63 | } 64 | 65 | impl From for LunaError { 66 | fn from(value: SessionError) -> Self { 67 | Self::Session(value) 68 | } 69 | } 70 | 71 | impl From for LunaError { 72 | fn from(value: JsonError) -> Self { 73 | Self::Io(IoError::new( 74 | ErrorKind::InvalidData, 75 | format!("Invalid JSON: {value:?}"), 76 | )) 77 | } 78 | } 79 | 80 | impl From for LunaError { 81 | fn from(value: IoError) -> Self { 82 | Self::Io(value) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /common/connection/src/luna/message.rs: -------------------------------------------------------------------------------- 1 | use crate::luna::Message; 2 | use serde::de::DeserializeOwned; 3 | 4 | impl Message { 5 | pub fn deserialize(self) -> Result { 6 | serde_json::from_value(self.value) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /common/connection/src/luna/mod.rs: -------------------------------------------------------------------------------- 1 | use std::io::Error as IoError; 2 | 3 | use libssh_rs::Channel; 4 | use serde::de::DeserializeOwned; 5 | use serde::Serialize; 6 | use serde_json::Value; 7 | 8 | use crate::session::SessionError; 9 | 10 | mod luna; 11 | mod message; 12 | mod subscription; 13 | 14 | pub trait Luna { 15 | fn call(&self, uri: &str, payload: P, public: bool) -> Result 16 | where 17 | P: Sized + Serialize, 18 | R: DeserializeOwned; 19 | 20 | fn subscribe

(&self, uri: &str, payload: P, public: bool) -> Result 21 | where 22 | P: Sized + Serialize; 23 | } 24 | 25 | #[derive(Debug)] 26 | pub enum LunaError { 27 | Session(SessionError), 28 | Io(IoError), 29 | NotAvailable, 30 | } 31 | 32 | pub struct Subscription { 33 | ch: Channel, 34 | buffer: Vec, 35 | } 36 | 37 | #[derive(Debug)] 38 | pub struct Message { 39 | value: Value, 40 | } 41 | 42 | #[derive(Serialize, Default)] 43 | pub struct LunaEmptyPayload {} 44 | -------------------------------------------------------------------------------- /common/connection/src/luna/subscription.rs: -------------------------------------------------------------------------------- 1 | use crate::luna::{Message, Subscription}; 2 | use serde_json::Value; 3 | use std::io::{Error, ErrorKind}; 4 | use std::time::Duration; 5 | 6 | impl Iterator for Subscription { 7 | type Item = std::io::Result; 8 | 9 | fn next(&mut self) -> Option { 10 | if self.ch.is_closed() || self.ch.is_eof() { 11 | return None; 12 | } 13 | let item: serde_json::Result; 14 | loop { 15 | let mut buffer = [0; 1024]; 16 | match self 17 | .ch 18 | .read_timeout(&mut buffer, false, Some(Duration::from_millis(10))) 19 | { 20 | Ok(len) => { 21 | self.buffer.extend_from_slice(&buffer[..len]); 22 | } 23 | Err(e) => { 24 | return Some(Err(Error::new( 25 | ErrorKind::Other, 26 | format!("SSH read error: {e:?}"), 27 | ))); 28 | } 29 | } 30 | if self.buffer.is_empty() { 31 | continue; 32 | } 33 | if let Some(idx) = self.buffer.iter().position(|&r| r == b'\n') { 34 | item = serde_json::from_slice(&self.buffer[..idx]); 35 | self.buffer.drain(..idx + 1); 36 | break; 37 | } 38 | } 39 | Some( 40 | item.map_err(|e| { 41 | Error::new(ErrorKind::InvalidData, format!("Bad JSON response: {e:?}")) 42 | }) 43 | .map(|value| Message { value }), 44 | ) 45 | } 46 | } 47 | 48 | impl Drop for Subscription { 49 | fn drop(&mut self) { 50 | self.close().unwrap_or_else(|e| { 51 | eprintln!("Failed to close subscription: {e:?}"); 52 | return 0; 53 | }); 54 | } 55 | } 56 | 57 | impl Subscription { 58 | fn close(&mut self) -> Result { 59 | self.ch.send_eof()?; 60 | self.ch.request_send_signal("TERM")?; 61 | let status = self.ch.get_exit_status(); 62 | self.ch.close()?; 63 | Ok(status.unwrap_or(-1) as i32) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /common/connection/src/session.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | use std::io::Error as IoError; 3 | use std::ops::Deref; 4 | use std::time::Duration; 5 | 6 | use libssh_rs::{AuthStatus, Error as SshError, Session, SshKey, SshOption}; 7 | 8 | use ares_device_lib::Device; 9 | 10 | pub trait NewSession { 11 | fn new_session(&self) -> Result; 12 | } 13 | 14 | pub struct DeviceSession { 15 | pub device: Device, 16 | pub session: Session, 17 | } 18 | 19 | #[derive(Debug)] 20 | pub enum SessionError { 21 | Io(IoError), 22 | LibSsh(SshError), 23 | Authorization { message: String }, 24 | } 25 | 26 | impl NewSession for Device { 27 | fn new_session(&self) -> Result { 28 | let kex = vec![ 29 | "curve25519-sha256", 30 | "curve25519-sha256@libssh.org", 31 | "ecdh-sha2-nistp256", 32 | "ecdh-sha2-nistp384", 33 | "ecdh-sha2-nistp521", 34 | "diffie-hellman-group18-sha512", 35 | "diffie-hellman-group16-sha512", 36 | "diffie-hellman-group-exchange-sha256", 37 | "diffie-hellman-group14-sha256", 38 | "diffie-hellman-group1-sha1", 39 | "diffie-hellman-group14-sha1", 40 | ]; 41 | let hmac = vec![ 42 | "hmac-sha2-256-etm@openssh.com", 43 | "hmac-sha2-512-etm@openssh.com", 44 | "hmac-sha2-256", 45 | "hmac-sha2-512", 46 | "hmac-sha1-96", 47 | "hmac-sha1", 48 | "hmac-md5", 49 | ]; 50 | let key_types = vec![ 51 | "ssh-ed25519", 52 | "ecdsa-sha2-nistp521", 53 | "ecdsa-sha2-nistp384", 54 | "ecdsa-sha2-nistp256", 55 | "rsa-sha2-512", 56 | "rsa-sha2-256", 57 | "ssh-rsa", 58 | ]; 59 | let session = Session::new()?; 60 | session.set_option(SshOption::Timeout(Duration::from_secs(10)))?; 61 | session.set_option(SshOption::Hostname(self.host.clone()))?; 62 | session.set_option(SshOption::Port(self.port.clone()))?; 63 | session.set_option(SshOption::User(Some(self.username.clone())))?; 64 | session.set_option(SshOption::KeyExchange(kex.join(",")))?; 65 | session.set_option(SshOption::HmacCS(hmac.join(",")))?; 66 | session.set_option(SshOption::HmacSC(hmac.join(",")))?; 67 | session.set_option(SshOption::HostKeys(key_types.join(",")))?; 68 | session.set_option(SshOption::PublicKeyAcceptedTypes(key_types.join(",")))?; 69 | session.set_option(SshOption::ProcessConfig(false))?; 70 | #[cfg(windows)] 71 | { 72 | session.set_option(SshOption::KnownHosts(Some("C:\\nul".to_string())))?; 73 | session.set_option(SshOption::GlobalKnownHosts(Some("C:\\nul".to_string())))?; 74 | } 75 | 76 | #[cfg(not(windows))] 77 | { 78 | session.set_option(SshOption::KnownHosts(Some(format!("/dev/null"))))?; 79 | session.set_option(SshOption::GlobalKnownHosts(Some(format!("/dev/null"))))?; 80 | } 81 | 82 | session.connect()?; 83 | 84 | if let Some(private_key) = &self.private_key { 85 | let passphrase = self.valid_passphrase(); 86 | let priv_key_content = private_key.content()?; 87 | let priv_key = SshKey::from_privkey_base64(&priv_key_content, passphrase.as_deref())?; 88 | 89 | if session.userauth_publickey(None, &priv_key)? != AuthStatus::Success { 90 | return Err(SessionError::Authorization { 91 | message: "Key authorization failed".to_string(), 92 | }); 93 | } 94 | } else if let Some(password) = &self.password { 95 | if session.userauth_password(None, Some(password))? != AuthStatus::Success { 96 | return Err(SessionError::Authorization { 97 | message: "Bad SSH password".to_string(), 98 | }); 99 | } 100 | } else if session.userauth_none(None)? != AuthStatus::Success { 101 | return Err(SessionError::Authorization { 102 | message: "Host needs authorization".to_string(), 103 | }); 104 | } 105 | Ok(DeviceSession { 106 | device: self.clone(), 107 | session, 108 | }) 109 | } 110 | } 111 | 112 | impl Deref for DeviceSession { 113 | type Target = Session; 114 | 115 | fn deref(&self) -> &Self::Target { 116 | &self.session 117 | } 118 | } 119 | 120 | impl From for SessionError { 121 | fn from(value: SshError) -> Self { 122 | SessionError::LibSsh(value) 123 | } 124 | } 125 | 126 | impl From for SessionError { 127 | fn from(value: IoError) -> Self { 128 | SessionError::Io(value) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /common/connection/src/setup.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Error, ErrorKind}; 2 | 3 | use libssh_rs::SshKey; 4 | 5 | use ares_device_lib::DeviceManager; 6 | 7 | use crate::DeviceSetupManager; 8 | 9 | impl DeviceSetupManager for DeviceManager { 10 | //noinspection HttpUrlsUsage 11 | fn novacom_getkey(&self, address: &str, passphrase: &str) -> Result { 12 | let content = reqwest::blocking::get(format!("http://{}:9991/webos_rsa", address)) 13 | .and_then(|res| res.error_for_status()) 14 | .and_then(|res| res.text()) 15 | .map_err(|e| { 16 | Error::new( 17 | ErrorKind::Other, 18 | format!("Can't request private key: {e:?}"), 19 | ) 20 | })?; 21 | 22 | match SshKey::from_privkey_base64(&content, Some(passphrase)) { 23 | Ok(_) => Ok(content), 24 | _ => Err(if passphrase.is_empty() { 25 | Error::new(ErrorKind::Other, "Passphrase is empty".to_string()) 26 | } else { 27 | Error::new(ErrorKind::Other, "Passphrase is incorrect".to_string()) 28 | }), 29 | } 30 | } 31 | 32 | fn localkey_verify(&self, name: &str, passphrase: &str) -> Result<(), Error> { 33 | todo!(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /common/connection/src/transfer.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Error as IoError, Read, Write}; 2 | use std::path::Path; 3 | 4 | use libssh_rs::{Error as SshError, FileType}; 5 | use libssh_rs::{OpenFlags, Sftp}; 6 | use path_slash::PathExt; 7 | 8 | use ares_device_lib::FileTransfer::Stream; 9 | 10 | use crate::session::DeviceSession; 11 | 12 | pub trait FileTransfer { 13 | fn maybe_sftp(&self) -> Result; 14 | fn mkdir>(&self, dir: &mut P, mode: u32) -> Result<(), TransferError>; 15 | fn put, R: Read, F: Fn(usize)>( 16 | &self, 17 | source: &mut R, 18 | target: P, 19 | progress: F, 20 | ) -> Result<(), TransferError>; 21 | fn get, W: Write>(&self, source: P, target: &mut W) 22 | -> Result<(), TransferError>; 23 | 24 | fn rm>(&self, path: P) -> Result<(), TransferError>; 25 | } 26 | 27 | #[derive(Debug)] 28 | pub enum TransferError { 29 | ExitCode { code: i32, reason: String }, 30 | Ssh(SshError), 31 | Io(IoError), 32 | } 33 | 34 | impl FileTransfer for DeviceSession { 35 | fn maybe_sftp(&self) -> Result { 36 | if Some(Stream) == self.device.files { 37 | return Err(libssh_rs::Error::RequestDenied( 38 | "SFTP is not supported".to_string(), 39 | )); 40 | } 41 | self.sftp() 42 | } 43 | fn mkdir>(&self, dir: &mut P, mode: u32) -> Result<(), TransferError> { 44 | if let Ok(sftp) = self.maybe_sftp() { 45 | if let Ok(Some(file_type)) = sftp 46 | .metadata(dir.as_ref().to_slash_lossy().as_ref()) 47 | .map(|m| m.file_type()) 48 | { 49 | if file_type == FileType::Directory { 50 | return Ok(()); 51 | } 52 | return Err(TransferError::ExitCode { 53 | code: 1, 54 | reason: format!( 55 | "File {} exists and is not a directory", 56 | dir.as_ref().to_slash_lossy() 57 | ), 58 | }); 59 | } 60 | sftp.create_dir(dir.as_ref().to_slash_lossy().as_ref(), mode)?; 61 | } else { 62 | let ch = self.new_channel()?; 63 | ch.open_session()?; 64 | ch.request_exec(&format!( 65 | "mkdir -p {} && chmod {mode:o} {}", 66 | snailquote::escape(dir.as_ref().to_slash_lossy().as_ref()), 67 | snailquote::escape(dir.as_ref().to_slash_lossy().as_ref()) 68 | ))?; 69 | ch.send_eof()?; 70 | let result_code = ch.get_exit_status().unwrap_or(0) as i32; 71 | ch.close()?; 72 | if result_code != 0 { 73 | return Err(TransferError::ExitCode { 74 | code: result_code, 75 | reason: format!("mkdir command exited with status {result_code}"), 76 | }); 77 | } 78 | } 79 | Ok(()) 80 | } 81 | 82 | fn put, R: Read, F: Fn(usize)>( 83 | &self, 84 | source: &mut R, 85 | target: P, 86 | progress: F, 87 | ) -> Result<(), TransferError> { 88 | if let Ok(sftp) = self.maybe_sftp() { 89 | let mut file = sftp.open( 90 | target.as_ref().to_slash_lossy().as_ref(), 91 | OpenFlags::WRITE_ONLY | OpenFlags::CREATE | OpenFlags::TRUNCATE, 92 | 0o644, 93 | )?; 94 | copy_with_progress(source, &mut file, progress)?; 95 | } else { 96 | let ch = self.new_channel()?; 97 | ch.open_session()?; 98 | ch.request_exec(&format!( 99 | "cat > {}", 100 | snailquote::escape(target.as_ref().to_slash_lossy().as_ref()) 101 | ))?; 102 | copy_with_progress(source, &mut ch.stdin(), progress)?; 103 | ch.send_eof()?; 104 | let result_code = ch.get_exit_status().unwrap_or(0) as i32; 105 | ch.close()?; 106 | if result_code != 0 { 107 | return Err(TransferError::ExitCode { 108 | code: result_code, 109 | reason: format!("cat command exited with status {result_code}"), 110 | }); 111 | } 112 | } 113 | Ok(()) 114 | } 115 | 116 | fn get, W: Write>( 117 | &self, 118 | source: P, 119 | target: &mut W, 120 | ) -> Result<(), TransferError> { 121 | if let Ok(sftp) = self.maybe_sftp() { 122 | let mut file = sftp.open( 123 | source.as_ref().to_slash_lossy().as_ref(), 124 | OpenFlags::READ_ONLY, 125 | 0, 126 | )?; 127 | std::io::copy(&mut file, target)?; 128 | } else { 129 | let ch = self.new_channel()?; 130 | ch.open_session()?; 131 | ch.request_exec(&format!( 132 | "cat {}", 133 | snailquote::escape(source.as_ref().to_slash_lossy().as_ref()) 134 | ))?; 135 | std::io::copy(&mut ch.stdout(), target)?; 136 | let result_code = ch.get_exit_status().unwrap_or(0) as i32; 137 | ch.close()?; 138 | if result_code != 0 { 139 | return Err(TransferError::ExitCode { 140 | code: result_code, 141 | reason: format!("cat command exited with status {result_code}"), 142 | }); 143 | } 144 | } 145 | Ok(()) 146 | } 147 | 148 | fn rm>(&self, path: P) -> Result<(), TransferError> { 149 | if let Ok(sftp) = self.maybe_sftp() { 150 | sftp.remove_file(path.as_ref().to_slash_lossy().as_ref())?; 151 | } else { 152 | let ch = self.new_channel()?; 153 | ch.open_session()?; 154 | ch.request_exec(&format!( 155 | "rm -rf {}", 156 | snailquote::escape(path.as_ref().to_slash_lossy().as_ref()) 157 | ))?; 158 | ch.send_eof()?; 159 | let result_code = ch.get_exit_status().unwrap_or(0) as i32; 160 | ch.close()?; 161 | if result_code != 0 { 162 | return Err(TransferError::ExitCode { 163 | code: result_code, 164 | reason: format!("rm command exited with status {result_code}"), 165 | }); 166 | } 167 | } 168 | Ok(()) 169 | } 170 | } 171 | 172 | impl From for TransferError { 173 | fn from(value: IoError) -> Self { 174 | Self::Io(value) 175 | } 176 | } 177 | impl From for TransferError { 178 | fn from(value: SshError) -> Self { 179 | Self::Ssh(value) 180 | } 181 | } 182 | 183 | fn copy_with_progress( 184 | source: &mut R, 185 | target: &mut W, 186 | progress: F, 187 | ) -> Result<(), TransferError> { 188 | let mut buffer = [0u8; 1024 * 8]; 189 | let mut total = 0usize; 190 | loop { 191 | let read = source.read(&mut buffer)?; 192 | if read == 0 { 193 | break; 194 | } 195 | target.write_all(&buffer[..read])?; 196 | total += read; 197 | progress(total); 198 | } 199 | Ok(()) 200 | } 201 | -------------------------------------------------------------------------------- /common/device/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "common-device" 3 | version = "0.1.1" 4 | edition = "2021" 5 | 6 | [lib] 7 | name = "ares_device_lib" 8 | 9 | [dependencies] 10 | serde = { workspace = true, features = ["derive"] } 11 | serde_json = { workspace = true } 12 | home = "0.5.5" 13 | log = "0.4.20" 14 | pathdiff = "0.2.1" 15 | 16 | [features] 17 | picker = [] -------------------------------------------------------------------------------- /common/device/src/device.rs: -------------------------------------------------------------------------------- 1 | use crate::Device; 2 | 3 | impl AsRef for Device { 4 | fn as_ref(&self) -> &Device { 5 | self 6 | } 7 | } 8 | 9 | impl Device { 10 | pub fn valid_passphrase(&self) -> Option { 11 | self.passphrase.clone().filter(|s| !s.is_empty()) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /common/device/src/io.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_family = "windows")] 2 | use std::env; 3 | use std::fs; 4 | use std::fs::{create_dir_all, File}; 5 | use std::io::{BufReader, BufWriter, Error, ErrorKind}; 6 | use std::path::PathBuf; 7 | 8 | use home::home_dir; 9 | use serde_json::Value; 10 | 11 | use crate::Device; 12 | 13 | pub(crate) fn read() -> Result, Error> { 14 | let path = devices_file_path()?; 15 | let file = match File::open(path.as_path()) { 16 | Ok(file) => file, 17 | Err(e) => { 18 | return match e.kind() { 19 | ErrorKind::NotFound => Ok(Vec::new()), 20 | _ => Err(e.into()), 21 | }; 22 | } 23 | }; 24 | let reader = BufReader::new(file); 25 | 26 | let raw_list: Vec = serde_json::from_reader(reader)?; 27 | Ok(raw_list 28 | .iter() 29 | .filter_map(|v| serde_json::from_value::(v.clone()).ok()) 30 | .collect()) 31 | } 32 | 33 | pub(crate) fn write(devices: Vec) -> Result<(), Error> { 34 | let path = devices_file_path()?; 35 | let file = match File::create(path.as_path()) { 36 | Ok(file) => file, 37 | Err(e) => { 38 | match e.kind() { 39 | ErrorKind::PermissionDenied => { 40 | fix_devices_json_perm(path.clone())?; 41 | } 42 | ErrorKind::NotFound => { 43 | let parent = path.parent().ok_or(Error::from(ErrorKind::NotFound))?; 44 | create_dir_all(parent)?; 45 | } 46 | _ => return Err(e.into()), 47 | } 48 | File::create(path.as_path())? 49 | } 50 | }; 51 | log::info!("make the file writable: {:?}", path); 52 | file.metadata()?.permissions().set_readonly(false); 53 | let writer = BufWriter::new(file); 54 | serde_json::to_writer_pretty(writer, &devices)?; 55 | Ok(()) 56 | } 57 | 58 | pub(crate) fn ssh_dir() -> Result { 59 | home_dir() 60 | .map(|d| d.join(".ssh")) 61 | .ok_or(Error::new(ErrorKind::NotFound, "SSH directory not found")) 62 | } 63 | 64 | pub(crate) fn ensure_ssh_dir() -> Result { 65 | let dir = ssh_dir()?; 66 | if !dir.exists() { 67 | create_dir_all(dir.clone())?; 68 | } 69 | Ok(dir) 70 | } 71 | 72 | #[cfg(target_family = "windows")] 73 | fn devices_file_path() -> Result { 74 | let home = env::var("APPDATA") 75 | .or_else(|_| env::var("USERPROFILE")) 76 | .map_err(|_| Error::new(ErrorKind::NotFound, "Can't find %AppData% or %UserProfile%"))?; 77 | Ok(PathBuf::from(home) 78 | .join(".webos") 79 | .join("ose") 80 | .join("novacom-devices.json")) 81 | } 82 | 83 | #[cfg(not(unix))] 84 | fn fix_devices_json_perm(path: PathBuf) -> Result<(), Error> { 85 | let mut perm = fs::metadata(path.clone())?.permissions(); 86 | perm.set_readonly(false); 87 | fs::set_permissions(path, perm)?; 88 | Ok(()) 89 | } 90 | 91 | #[cfg(not(target_family = "windows"))] 92 | fn devices_file_path() -> Result { 93 | let home = 94 | home_dir().ok_or_else(|| Error::new(ErrorKind::NotFound, "Can't find home directory"))?; 95 | return Ok(home.join(".webos").join("ose").join("novacom-devices.json")); 96 | } 97 | 98 | #[cfg(unix)] 99 | fn fix_devices_json_perm(path: PathBuf) -> Result<(), Error> { 100 | use std::os::unix::fs::PermissionsExt; 101 | let perm = fs::Permissions::from_mode(0o644); 102 | fs::set_permissions(path, perm)?; 103 | return Ok(()); 104 | } 105 | -------------------------------------------------------------------------------- /common/device/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Mutex; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | mod device; 6 | mod io; 7 | mod manager; 8 | mod privkey; 9 | 10 | #[derive(Default)] 11 | pub struct DeviceManager { 12 | devices: Mutex>, 13 | } 14 | 15 | #[derive(Serialize, Deserialize, Clone, Debug)] 16 | pub struct Device { 17 | #[serde(skip_serializing_if = "Option::is_none")] 18 | pub order: Option, 19 | #[serde(skip_serializing_if = "Option::is_none")] 20 | pub default: Option, 21 | pub profile: String, 22 | pub name: String, 23 | #[serde(skip_serializing_if = "Option::is_none")] 24 | pub description: Option, 25 | pub host: String, 26 | pub port: u16, 27 | pub username: String, 28 | #[serde(default, skip_serializing)] 29 | pub(crate) new: bool, 30 | #[serde(rename = "privateKey", skip_serializing_if = "Option::is_none")] 31 | pub private_key: Option, 32 | #[serde(default, skip_serializing_if = "Option::is_none")] 33 | pub files: Option, 34 | #[serde(skip_serializing_if = "Option::is_none")] 35 | pub passphrase: Option, 36 | #[serde(skip_serializing_if = "Option::is_none")] 37 | pub password: Option, 38 | #[serde(rename = "logDaemon", skip_serializing_if = "Option::is_none")] 39 | pub log_daemon: Option, 40 | #[serde( 41 | rename = "noPortForwarding", 42 | default, 43 | skip_serializing_if = "Option::is_none" 44 | )] 45 | pub no_port_forwarding: Option, 46 | #[serde(default, skip_serializing_if = "Option::is_none")] 47 | pub indelible: Option, 48 | } 49 | 50 | #[derive(Serialize, Deserialize, Clone, Debug)] 51 | #[serde(untagged)] 52 | pub enum PrivateKey { 53 | Name { 54 | #[serde(rename = "openSsh")] 55 | name: String, 56 | }, 57 | Path { 58 | #[serde(rename = "openSshPath")] 59 | path: String, 60 | }, 61 | } 62 | #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] 63 | pub enum FileTransfer { 64 | #[serde(rename = "stream")] 65 | Stream, 66 | #[serde(rename = "sftp")] 67 | Sftp, 68 | } 69 | 70 | pub fn add(left: usize, right: usize) -> usize { 71 | left + right 72 | } 73 | 74 | #[cfg(test)] 75 | mod tests { 76 | use super::*; 77 | 78 | #[test] 79 | fn it_works() { 80 | let result = add(2, 2); 81 | assert_eq!(result, 4); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /common/device/src/manager.rs: -------------------------------------------------------------------------------- 1 | use std::fs::remove_file; 2 | use std::io::{Error, ErrorKind}; 3 | use std::path::Path; 4 | 5 | use crate::io::{ensure_ssh_dir, read, write}; 6 | use crate::{Device, DeviceManager, PrivateKey}; 7 | 8 | impl DeviceManager { 9 | pub fn list(&self) -> Result, Error> { 10 | read() 11 | } 12 | 13 | pub fn find_or_default>(&self, name: Option) -> Result, Error> { 14 | let devices = self.list()?; 15 | Ok(devices 16 | .iter() 17 | .find(|d| { 18 | if let Some(name) = &name { 19 | &d.name == name.as_ref() 20 | } else { 21 | d.default.unwrap_or(false) 22 | } 23 | }) 24 | .cloned()) 25 | } 26 | 27 | pub fn set_default(&self, name: &str) -> Result, Error> { 28 | let mut devices = read()?; 29 | let mut result: Option = None; 30 | for device in &mut devices { 31 | if device.name == name { 32 | device.default = Some(true); 33 | result = Some(device.clone()); 34 | } else { 35 | device.default = None; 36 | } 37 | } 38 | log::trace!("{:?}", devices); 39 | write(devices)?; 40 | Ok(result) 41 | } 42 | 43 | pub fn add(&self, device: &Device) -> Result { 44 | let mut device = device.clone(); 45 | match &device.private_key { 46 | Some(PrivateKey::Path { path }) => { 47 | let path = Path::new(path); 48 | if path.is_absolute() { 49 | let name = String::from( 50 | pathdiff::diff_paths(path, ensure_ssh_dir()?) 51 | .ok_or(Error::from(ErrorKind::NotFound))? 52 | .to_string_lossy(), 53 | ); 54 | device.private_key = Some(PrivateKey::Name { name }); 55 | } 56 | } 57 | _ => {} 58 | } 59 | log::info!("Save device {}", device.name); 60 | let mut devices = read()?; 61 | devices.push(device.clone()); 62 | write(devices.clone())?; 63 | Ok(device) 64 | } 65 | 66 | pub fn remove(&self, name: &str, remove_key: bool) -> Result<(), Error> { 67 | let devices = read()?; 68 | let (will_delete, mut will_keep): (Vec, Vec) = 69 | devices.into_iter().partition(|d| d.name == name); 70 | let mut need_new_default = false; 71 | if remove_key { 72 | for device in will_delete { 73 | if device.default.unwrap_or(false) { 74 | need_new_default = true; 75 | } 76 | if let Some(name) = device.private_key.and_then(|k| match k { 77 | PrivateKey::Name { name } => Some(name), 78 | _ => None, 79 | }) { 80 | if !name.starts_with("webos_") { 81 | continue; 82 | } 83 | let key_path = ensure_ssh_dir()?.join(name); 84 | remove_file(key_path)?; 85 | } 86 | } 87 | } 88 | if need_new_default && !will_keep.is_empty() { 89 | will_keep.first_mut().unwrap().default = Some(true); 90 | } 91 | write(will_keep)?; 92 | Ok(()) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /common/device/src/privkey.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::{Error, Read}; 3 | 4 | use crate::io::ssh_dir; 5 | use crate::PrivateKey; 6 | 7 | impl PrivateKey { 8 | pub fn content(&self) -> Result { 9 | match self { 10 | PrivateKey::Name { name } => { 11 | let mut secret_file = File::open(ssh_dir()?.join(name))?; 12 | let mut secret = String::new(); 13 | secret_file.read_to_string(&mut secret)?; 14 | Ok(secret) 15 | } 16 | PrivateKey::Path { path } => { 17 | let mut secret_file = File::open(path)?; 18 | let mut secret = String::new(); 19 | secret_file.read_to_string(&mut secret)?; 20 | Ok(secret) 21 | } 22 | } 23 | } 24 | } 25 | --------------------------------------------------------------------------------