├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md └── workflows │ └── docker-publish.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE.md ├── README.md ├── build.rs ├── docker-compose.yml ├── dockerfile ├── install-docs ├── host-on-oracle-cloud-instructions.md ├── jitstreamer-eb-debian-docker-instructions.md ├── jitstreamer-eb-debian-docker-tailscale-instructions.md ├── jitstreamer-eb-ubuntu-raspberry-pi-instructions.md ├── macos.md └── unraid-os-install-instructions.md ├── jitstreamer.sh ├── justfile └── src ├── common.rs ├── db.rs ├── heartbeat.rs ├── main.rs ├── mount.html ├── mount.rs ├── netmuxd.rs ├── raw_packet.rs ├── register.rs ├── sql └── up.sql └── upload.html /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Scope** 11 | Submitting an issue to this repository is for the code base itself, not for support with the public instance. By submitting an issue, you are declaring that you are **SELF-HOSTING** your own instance and will provide logs for said instance. 12 | 13 | I have read the scope: [yes/no] 14 | 15 | **Describe the bug** 16 | A clear and concise description of what the bug is. 17 | 18 | **NAT** 19 | Describe your NAT here. If you're not sure what your NAT setup is, there's a solid chance that's your problem. 20 | 21 | **To Reproduce** 22 | Steps to reproduce the behavior: 23 | 1. Go to '...' 24 | 2. Click on '....' 25 | 3. Scroll down to '....' 26 | 4. See error 27 | 28 | **Expected behavior** 29 | A clear and concise description of what you expected to happen. 30 | 31 | **Screenshots** 32 | If applicable, add screenshots to help explain your problem. 33 | 34 | # Logs 35 | 36 | Please run each of the binaries with the environment variable ``RUST_LOG=debug`` and paste them below. 37 | 38 | **JitStreamer-EB** 39 | Post your JitStreamer-EB logs here 40 | 41 | **netmuxd** 42 | Post your netmuxd logs here 43 | 44 | **tunneld** 45 | Post your tunneld logs here 46 | 47 | **Additional context** 48 | Add any other context about the problem here. 49 | -------------------------------------------------------------------------------- /.github/workflows/docker-publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish container image 2 | 3 | # This workflow uses actions that are not certified by GitHub. 4 | # They are provided by a third-party and are governed by 5 | # separate terms of service, privacy policy, and support 6 | # documentation. 7 | 8 | on: 9 | push: 10 | branches: [ "master" ] 11 | # Publish semver tags as releases. 12 | tags: [ 'v*.*.*' ] 13 | pull_request: 14 | branches: [ "master" ] 15 | 16 | env: 17 | # Use docker.io for Docker Hub if empty 18 | REGISTRY: ghcr.io 19 | # github.repository as / 20 | IMAGE_NAME: ${{ github.repository }} 21 | 22 | 23 | jobs: 24 | build: 25 | 26 | runs-on: ubuntu-latest 27 | permissions: 28 | contents: read 29 | packages: write 30 | # This is used to complete the identity challenge 31 | # with sigstore/fulcio when running outside of PRs. 32 | id-token: write 33 | 34 | steps: 35 | - name: Checkout repository 36 | uses: actions/checkout@v4 37 | 38 | # Install the cosign tool except on PR 39 | # https://github.com/sigstore/cosign-installer 40 | - name: Install cosign 41 | if: github.event_name != 'pull_request' 42 | uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 #v3.5.0 43 | with: 44 | cosign-release: 'v2.2.4' 45 | 46 | # Set up BuildKit Docker container builder to be able to build 47 | # multi-platform images and export cache 48 | # https://github.com/docker/setup-buildx-action 49 | - name: Set up Docker Buildx 50 | uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 51 | 52 | # Login against a Docker registry except on PR 53 | # https://github.com/docker/login-action 54 | - name: Log into registry ${{ env.REGISTRY }} 55 | if: github.event_name != 'pull_request' 56 | uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 57 | with: 58 | registry: ${{ env.REGISTRY }} 59 | username: ${{ github.actor }} 60 | password: ${{ secrets.GITHUB_TOKEN }} 61 | 62 | # Extract metadata (tags, labels) for Docker 63 | # https://github.com/docker/metadata-action 64 | - name: Extract Docker metadata 65 | id: meta 66 | uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0 67 | with: 68 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 69 | 70 | # Build and push Docker image with Buildx (don't push on PR) 71 | # https://github.com/docker/build-push-action 72 | - name: Build and push Docker image 73 | id: build-and-push 74 | uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0 75 | with: 76 | context: . 77 | push: ${{ github.event_name != 'pull_request' }} 78 | tags: ${{ steps.meta.outputs.tags }} 79 | labels: ${{ steps.meta.outputs.labels }} 80 | cache-from: type=gha 81 | cache-to: type=gha,mode=max 82 | platforms: linux/amd64,linux/arm64 #,linux/arm/v7 83 | 84 | # Sign the resulting Docker image digest except on PRs. 85 | # This will only write to the public Rekor transparency log when the Docker 86 | # repository is public to avoid leaking data. If you would like to publish 87 | # transparency data even for private images, pass --force to cosign below. 88 | # https://github.com/sigstore/cosign 89 | - name: Sign the published Docker image 90 | if: ${{ github.event_name != 'pull_request' }} 91 | env: 92 | # https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable 93 | TAGS: ${{ steps.meta.outputs.tags }} 94 | DIGEST: ${{ steps.build-and-push.outputs.digest }} 95 | # This step uses the identity token to provision an ephemeral certificate 96 | # against the sigstore community Fulcio instance. 97 | run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST} 98 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | *.db 3 | .env 4 | __pycache__ 5 | prod.sh 6 | /DDI 7 | .DS_Store 8 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 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.7" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" 73 | dependencies = [ 74 | "anstyle", 75 | "once_cell", 76 | "windows-sys 0.59.0", 77 | ] 78 | 79 | [[package]] 80 | name = "async-recursion" 81 | version = "1.1.1" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" 84 | dependencies = [ 85 | "proc-macro2", 86 | "quote", 87 | "syn", 88 | ] 89 | 90 | [[package]] 91 | name = "atomic-waker" 92 | version = "1.1.2" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 95 | 96 | [[package]] 97 | name = "autocfg" 98 | version = "1.4.0" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 101 | 102 | [[package]] 103 | name = "axum" 104 | version = "0.8.1" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8" 107 | dependencies = [ 108 | "axum-core", 109 | "axum-macros", 110 | "base64", 111 | "bytes", 112 | "form_urlencoded", 113 | "futures-util", 114 | "http", 115 | "http-body", 116 | "http-body-util", 117 | "hyper", 118 | "hyper-util", 119 | "itoa", 120 | "matchit", 121 | "memchr", 122 | "mime", 123 | "percent-encoding", 124 | "pin-project-lite", 125 | "rustversion", 126 | "serde", 127 | "serde_json", 128 | "serde_path_to_error", 129 | "serde_urlencoded", 130 | "sha1", 131 | "sync_wrapper", 132 | "tokio", 133 | "tokio-tungstenite", 134 | "tower", 135 | "tower-layer", 136 | "tower-service", 137 | "tracing", 138 | ] 139 | 140 | [[package]] 141 | name = "axum-client-ip" 142 | version = "0.7.0" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "dff8ee1869817523c8f91c20bf17fd932707f66c2e7e0b0f811b29a227289562" 145 | dependencies = [ 146 | "axum", 147 | "forwarded-header-value", 148 | "serde", 149 | ] 150 | 151 | [[package]] 152 | name = "axum-core" 153 | version = "0.5.0" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "df1362f362fd16024ae199c1970ce98f9661bf5ef94b9808fee734bc3698b733" 156 | dependencies = [ 157 | "bytes", 158 | "futures-util", 159 | "http", 160 | "http-body", 161 | "http-body-util", 162 | "mime", 163 | "pin-project-lite", 164 | "rustversion", 165 | "sync_wrapper", 166 | "tower-layer", 167 | "tower-service", 168 | "tracing", 169 | ] 170 | 171 | [[package]] 172 | name = "axum-macros" 173 | version = "0.5.0" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" 176 | dependencies = [ 177 | "proc-macro2", 178 | "quote", 179 | "syn", 180 | ] 181 | 182 | [[package]] 183 | name = "backtrace" 184 | version = "0.3.74" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 187 | dependencies = [ 188 | "addr2line", 189 | "cfg-if", 190 | "libc", 191 | "miniz_oxide", 192 | "object", 193 | "rustc-demangle", 194 | "windows-targets", 195 | ] 196 | 197 | [[package]] 198 | name = "base64" 199 | version = "0.22.1" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 202 | 203 | [[package]] 204 | name = "bitflags" 205 | version = "2.8.0" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" 208 | 209 | [[package]] 210 | name = "block-buffer" 211 | version = "0.10.4" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 214 | dependencies = [ 215 | "generic-array", 216 | ] 217 | 218 | [[package]] 219 | name = "bumpalo" 220 | version = "3.17.0" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" 223 | 224 | [[package]] 225 | name = "byteorder" 226 | version = "1.5.0" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 229 | 230 | [[package]] 231 | name = "bytes" 232 | version = "1.10.0" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" 235 | 236 | [[package]] 237 | name = "cc" 238 | version = "1.2.12" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "755717a7de9ec452bf7f3f1a3099085deabd7f2962b861dae91ecd7a365903d2" 241 | dependencies = [ 242 | "shlex", 243 | ] 244 | 245 | [[package]] 246 | name = "cfg-if" 247 | version = "1.0.0" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 250 | 251 | [[package]] 252 | name = "clap" 253 | version = "4.5.32" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83" 256 | dependencies = [ 257 | "clap_builder", 258 | "clap_derive", 259 | ] 260 | 261 | [[package]] 262 | name = "clap_builder" 263 | version = "4.5.32" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8" 266 | dependencies = [ 267 | "anstream", 268 | "anstyle", 269 | "clap_lex", 270 | "strsim", 271 | ] 272 | 273 | [[package]] 274 | name = "clap_derive" 275 | version = "4.5.32" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" 278 | dependencies = [ 279 | "heck", 280 | "proc-macro2", 281 | "quote", 282 | "syn", 283 | ] 284 | 285 | [[package]] 286 | name = "clap_lex" 287 | version = "0.7.4" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 290 | 291 | [[package]] 292 | name = "colorchoice" 293 | version = "1.0.3" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 296 | 297 | [[package]] 298 | name = "core-foundation" 299 | version = "0.9.4" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 302 | dependencies = [ 303 | "core-foundation-sys", 304 | "libc", 305 | ] 306 | 307 | [[package]] 308 | name = "core-foundation-sys" 309 | version = "0.8.7" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 312 | 313 | [[package]] 314 | name = "cpufeatures" 315 | version = "0.2.17" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 318 | dependencies = [ 319 | "libc", 320 | ] 321 | 322 | [[package]] 323 | name = "crypto-common" 324 | version = "0.1.6" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 327 | dependencies = [ 328 | "generic-array", 329 | "typenum", 330 | ] 331 | 332 | [[package]] 333 | name = "data-encoding" 334 | version = "2.7.0" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "0e60eed09d8c01d3cee5b7d30acb059b76614c918fa0f992e0dd6eeb10daad6f" 337 | 338 | [[package]] 339 | name = "deranged" 340 | version = "0.3.11" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" 343 | dependencies = [ 344 | "powerfmt", 345 | ] 346 | 347 | [[package]] 348 | name = "digest" 349 | version = "0.10.7" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 352 | dependencies = [ 353 | "block-buffer", 354 | "crypto-common", 355 | ] 356 | 357 | [[package]] 358 | name = "displaydoc" 359 | version = "0.2.5" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 362 | dependencies = [ 363 | "proc-macro2", 364 | "quote", 365 | "syn", 366 | ] 367 | 368 | [[package]] 369 | name = "dotenvy" 370 | version = "0.15.7" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" 373 | 374 | [[package]] 375 | name = "encoding_rs" 376 | version = "0.8.35" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" 379 | dependencies = [ 380 | "cfg-if", 381 | ] 382 | 383 | [[package]] 384 | name = "env_filter" 385 | version = "0.1.3" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" 388 | dependencies = [ 389 | "log", 390 | "regex", 391 | ] 392 | 393 | [[package]] 394 | name = "env_logger" 395 | version = "0.11.6" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" 398 | dependencies = [ 399 | "anstream", 400 | "anstyle", 401 | "env_filter", 402 | "humantime", 403 | "log", 404 | ] 405 | 406 | [[package]] 407 | name = "equivalent" 408 | version = "1.0.1" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 411 | 412 | [[package]] 413 | name = "errno" 414 | version = "0.3.10" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" 417 | dependencies = [ 418 | "libc", 419 | "windows-sys 0.59.0", 420 | ] 421 | 422 | [[package]] 423 | name = "fastrand" 424 | version = "2.3.0" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 427 | 428 | [[package]] 429 | name = "fnv" 430 | version = "1.0.7" 431 | source = "registry+https://github.com/rust-lang/crates.io-index" 432 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 433 | 434 | [[package]] 435 | name = "foreign-types" 436 | version = "0.3.2" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 439 | dependencies = [ 440 | "foreign-types-shared", 441 | ] 442 | 443 | [[package]] 444 | name = "foreign-types-shared" 445 | version = "0.1.1" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 448 | 449 | [[package]] 450 | name = "form_urlencoded" 451 | version = "1.2.1" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 454 | dependencies = [ 455 | "percent-encoding", 456 | ] 457 | 458 | [[package]] 459 | name = "forwarded-header-value" 460 | version = "0.1.1" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "8835f84f38484cc86f110a805655697908257fb9a7af005234060891557198e9" 463 | dependencies = [ 464 | "nonempty", 465 | "thiserror 1.0.69", 466 | ] 467 | 468 | [[package]] 469 | name = "futures" 470 | version = "0.3.31" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" 473 | dependencies = [ 474 | "futures-channel", 475 | "futures-core", 476 | "futures-executor", 477 | "futures-io", 478 | "futures-sink", 479 | "futures-task", 480 | "futures-util", 481 | ] 482 | 483 | [[package]] 484 | name = "futures-channel" 485 | version = "0.3.31" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 488 | dependencies = [ 489 | "futures-core", 490 | "futures-sink", 491 | ] 492 | 493 | [[package]] 494 | name = "futures-core" 495 | version = "0.3.31" 496 | source = "registry+https://github.com/rust-lang/crates.io-index" 497 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 498 | 499 | [[package]] 500 | name = "futures-executor" 501 | version = "0.3.31" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" 504 | dependencies = [ 505 | "futures-core", 506 | "futures-task", 507 | "futures-util", 508 | ] 509 | 510 | [[package]] 511 | name = "futures-io" 512 | version = "0.3.31" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 515 | 516 | [[package]] 517 | name = "futures-macro" 518 | version = "0.3.31" 519 | source = "registry+https://github.com/rust-lang/crates.io-index" 520 | checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 521 | dependencies = [ 522 | "proc-macro2", 523 | "quote", 524 | "syn", 525 | ] 526 | 527 | [[package]] 528 | name = "futures-sink" 529 | version = "0.3.31" 530 | source = "registry+https://github.com/rust-lang/crates.io-index" 531 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 532 | 533 | [[package]] 534 | name = "futures-task" 535 | version = "0.3.31" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 538 | 539 | [[package]] 540 | name = "futures-util" 541 | version = "0.3.31" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 544 | dependencies = [ 545 | "futures-channel", 546 | "futures-core", 547 | "futures-io", 548 | "futures-macro", 549 | "futures-sink", 550 | "futures-task", 551 | "memchr", 552 | "pin-project-lite", 553 | "pin-utils", 554 | "slab", 555 | ] 556 | 557 | [[package]] 558 | name = "generic-array" 559 | version = "0.14.7" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 562 | dependencies = [ 563 | "typenum", 564 | "version_check", 565 | ] 566 | 567 | [[package]] 568 | name = "getrandom" 569 | version = "0.2.15" 570 | source = "registry+https://github.com/rust-lang/crates.io-index" 571 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 572 | dependencies = [ 573 | "cfg-if", 574 | "libc", 575 | "wasi 0.11.0+wasi-snapshot-preview1", 576 | ] 577 | 578 | [[package]] 579 | name = "getrandom" 580 | version = "0.3.1" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" 583 | dependencies = [ 584 | "cfg-if", 585 | "libc", 586 | "wasi 0.13.3+wasi-0.2.2", 587 | "windows-targets", 588 | ] 589 | 590 | [[package]] 591 | name = "gimli" 592 | version = "0.31.1" 593 | source = "registry+https://github.com/rust-lang/crates.io-index" 594 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 595 | 596 | [[package]] 597 | name = "h2" 598 | version = "0.4.7" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" 601 | dependencies = [ 602 | "atomic-waker", 603 | "bytes", 604 | "fnv", 605 | "futures-core", 606 | "futures-sink", 607 | "http", 608 | "indexmap", 609 | "slab", 610 | "tokio", 611 | "tokio-util", 612 | "tracing", 613 | ] 614 | 615 | [[package]] 616 | name = "hashbrown" 617 | version = "0.15.2" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 620 | 621 | [[package]] 622 | name = "heck" 623 | version = "0.5.0" 624 | source = "registry+https://github.com/rust-lang/crates.io-index" 625 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 626 | 627 | [[package]] 628 | name = "http" 629 | version = "1.2.0" 630 | source = "registry+https://github.com/rust-lang/crates.io-index" 631 | checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" 632 | dependencies = [ 633 | "bytes", 634 | "fnv", 635 | "itoa", 636 | ] 637 | 638 | [[package]] 639 | name = "http-body" 640 | version = "1.0.1" 641 | source = "registry+https://github.com/rust-lang/crates.io-index" 642 | checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 643 | dependencies = [ 644 | "bytes", 645 | "http", 646 | ] 647 | 648 | [[package]] 649 | name = "http-body-util" 650 | version = "0.1.2" 651 | source = "registry+https://github.com/rust-lang/crates.io-index" 652 | checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" 653 | dependencies = [ 654 | "bytes", 655 | "futures-util", 656 | "http", 657 | "http-body", 658 | "pin-project-lite", 659 | ] 660 | 661 | [[package]] 662 | name = "httparse" 663 | version = "1.10.0" 664 | source = "registry+https://github.com/rust-lang/crates.io-index" 665 | checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" 666 | 667 | [[package]] 668 | name = "httpdate" 669 | version = "1.0.3" 670 | source = "registry+https://github.com/rust-lang/crates.io-index" 671 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 672 | 673 | [[package]] 674 | name = "humantime" 675 | version = "2.1.0" 676 | source = "registry+https://github.com/rust-lang/crates.io-index" 677 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 678 | 679 | [[package]] 680 | name = "hyper" 681 | version = "1.6.0" 682 | source = "registry+https://github.com/rust-lang/crates.io-index" 683 | checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" 684 | dependencies = [ 685 | "bytes", 686 | "futures-channel", 687 | "futures-util", 688 | "h2", 689 | "http", 690 | "http-body", 691 | "httparse", 692 | "httpdate", 693 | "itoa", 694 | "pin-project-lite", 695 | "smallvec", 696 | "tokio", 697 | "want", 698 | ] 699 | 700 | [[package]] 701 | name = "hyper-rustls" 702 | version = "0.27.5" 703 | source = "registry+https://github.com/rust-lang/crates.io-index" 704 | checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" 705 | dependencies = [ 706 | "futures-util", 707 | "http", 708 | "hyper", 709 | "hyper-util", 710 | "rustls", 711 | "rustls-pki-types", 712 | "tokio", 713 | "tokio-rustls", 714 | "tower-service", 715 | ] 716 | 717 | [[package]] 718 | name = "hyper-tls" 719 | version = "0.6.0" 720 | source = "registry+https://github.com/rust-lang/crates.io-index" 721 | checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" 722 | dependencies = [ 723 | "bytes", 724 | "http-body-util", 725 | "hyper", 726 | "hyper-util", 727 | "native-tls", 728 | "tokio", 729 | "tokio-native-tls", 730 | "tower-service", 731 | ] 732 | 733 | [[package]] 734 | name = "hyper-util" 735 | version = "0.1.10" 736 | source = "registry+https://github.com/rust-lang/crates.io-index" 737 | checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" 738 | dependencies = [ 739 | "bytes", 740 | "futures-channel", 741 | "futures-util", 742 | "http", 743 | "http-body", 744 | "hyper", 745 | "pin-project-lite", 746 | "socket2", 747 | "tokio", 748 | "tower-service", 749 | "tracing", 750 | ] 751 | 752 | [[package]] 753 | name = "icu_collections" 754 | version = "1.5.0" 755 | source = "registry+https://github.com/rust-lang/crates.io-index" 756 | checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" 757 | dependencies = [ 758 | "displaydoc", 759 | "yoke", 760 | "zerofrom", 761 | "zerovec", 762 | ] 763 | 764 | [[package]] 765 | name = "icu_locid" 766 | version = "1.5.0" 767 | source = "registry+https://github.com/rust-lang/crates.io-index" 768 | checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" 769 | dependencies = [ 770 | "displaydoc", 771 | "litemap", 772 | "tinystr", 773 | "writeable", 774 | "zerovec", 775 | ] 776 | 777 | [[package]] 778 | name = "icu_locid_transform" 779 | version = "1.5.0" 780 | source = "registry+https://github.com/rust-lang/crates.io-index" 781 | checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" 782 | dependencies = [ 783 | "displaydoc", 784 | "icu_locid", 785 | "icu_locid_transform_data", 786 | "icu_provider", 787 | "tinystr", 788 | "zerovec", 789 | ] 790 | 791 | [[package]] 792 | name = "icu_locid_transform_data" 793 | version = "1.5.0" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" 796 | 797 | [[package]] 798 | name = "icu_normalizer" 799 | version = "1.5.0" 800 | source = "registry+https://github.com/rust-lang/crates.io-index" 801 | checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" 802 | dependencies = [ 803 | "displaydoc", 804 | "icu_collections", 805 | "icu_normalizer_data", 806 | "icu_properties", 807 | "icu_provider", 808 | "smallvec", 809 | "utf16_iter", 810 | "utf8_iter", 811 | "write16", 812 | "zerovec", 813 | ] 814 | 815 | [[package]] 816 | name = "icu_normalizer_data" 817 | version = "1.5.0" 818 | source = "registry+https://github.com/rust-lang/crates.io-index" 819 | checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" 820 | 821 | [[package]] 822 | name = "icu_properties" 823 | version = "1.5.1" 824 | source = "registry+https://github.com/rust-lang/crates.io-index" 825 | checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" 826 | dependencies = [ 827 | "displaydoc", 828 | "icu_collections", 829 | "icu_locid_transform", 830 | "icu_properties_data", 831 | "icu_provider", 832 | "tinystr", 833 | "zerovec", 834 | ] 835 | 836 | [[package]] 837 | name = "icu_properties_data" 838 | version = "1.5.0" 839 | source = "registry+https://github.com/rust-lang/crates.io-index" 840 | checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" 841 | 842 | [[package]] 843 | name = "icu_provider" 844 | version = "1.5.0" 845 | source = "registry+https://github.com/rust-lang/crates.io-index" 846 | checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" 847 | dependencies = [ 848 | "displaydoc", 849 | "icu_locid", 850 | "icu_provider_macros", 851 | "stable_deref_trait", 852 | "tinystr", 853 | "writeable", 854 | "yoke", 855 | "zerofrom", 856 | "zerovec", 857 | ] 858 | 859 | [[package]] 860 | name = "icu_provider_macros" 861 | version = "1.5.0" 862 | source = "registry+https://github.com/rust-lang/crates.io-index" 863 | checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" 864 | dependencies = [ 865 | "proc-macro2", 866 | "quote", 867 | "syn", 868 | ] 869 | 870 | [[package]] 871 | name = "idevice" 872 | version = "0.1.26" 873 | source = "registry+https://github.com/rust-lang/crates.io-index" 874 | checksum = "4807b50215343d6e46886bfcfc37c9cdd6d1eb6b6b4b0ea8839b0918cddfecb4" 875 | dependencies = [ 876 | "async-recursion", 877 | "base64", 878 | "byteorder", 879 | "env_logger", 880 | "futures", 881 | "indexmap", 882 | "json", 883 | "log", 884 | "ns-keyed-archive", 885 | "openssl", 886 | "plist", 887 | "rand 0.9.0", 888 | "reqwest", 889 | "serde", 890 | "serde_json", 891 | "sha2", 892 | "thiserror 2.0.11", 893 | "tokio", 894 | "tokio-openssl", 895 | "uuid", 896 | ] 897 | 898 | [[package]] 899 | name = "idna" 900 | version = "1.0.3" 901 | source = "registry+https://github.com/rust-lang/crates.io-index" 902 | checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" 903 | dependencies = [ 904 | "idna_adapter", 905 | "smallvec", 906 | "utf8_iter", 907 | ] 908 | 909 | [[package]] 910 | name = "idna_adapter" 911 | version = "1.2.0" 912 | source = "registry+https://github.com/rust-lang/crates.io-index" 913 | checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" 914 | dependencies = [ 915 | "icu_normalizer", 916 | "icu_properties", 917 | ] 918 | 919 | [[package]] 920 | name = "indexmap" 921 | version = "2.7.1" 922 | source = "registry+https://github.com/rust-lang/crates.io-index" 923 | checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" 924 | dependencies = [ 925 | "equivalent", 926 | "hashbrown", 927 | "serde", 928 | ] 929 | 930 | [[package]] 931 | name = "ipnet" 932 | version = "2.11.0" 933 | source = "registry+https://github.com/rust-lang/crates.io-index" 934 | checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" 935 | 936 | [[package]] 937 | name = "ipnetwork" 938 | version = "0.20.0" 939 | source = "registry+https://github.com/rust-lang/crates.io-index" 940 | checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e" 941 | dependencies = [ 942 | "serde", 943 | ] 944 | 945 | [[package]] 946 | name = "is_terminal_polyfill" 947 | version = "1.70.1" 948 | source = "registry+https://github.com/rust-lang/crates.io-index" 949 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 950 | 951 | [[package]] 952 | name = "itoa" 953 | version = "1.0.14" 954 | source = "registry+https://github.com/rust-lang/crates.io-index" 955 | checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" 956 | 957 | [[package]] 958 | name = "jitstreamer-eb" 959 | version = "0.1.1" 960 | dependencies = [ 961 | "axum", 962 | "axum-client-ip", 963 | "axum-macros", 964 | "bytes", 965 | "dotenvy", 966 | "env_logger", 967 | "idevice", 968 | "log", 969 | "plist", 970 | "reqwest", 971 | "serde", 972 | "serde_json", 973 | "sha2", 974 | "sqlite", 975 | "tokio", 976 | "tower-http", 977 | "wg-config", 978 | ] 979 | 980 | [[package]] 981 | name = "js-sys" 982 | version = "0.3.77" 983 | source = "registry+https://github.com/rust-lang/crates.io-index" 984 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 985 | dependencies = [ 986 | "once_cell", 987 | "wasm-bindgen", 988 | ] 989 | 990 | [[package]] 991 | name = "json" 992 | version = "0.12.4" 993 | source = "registry+https://github.com/rust-lang/crates.io-index" 994 | checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" 995 | 996 | [[package]] 997 | name = "libc" 998 | version = "0.2.169" 999 | source = "registry+https://github.com/rust-lang/crates.io-index" 1000 | checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" 1001 | 1002 | [[package]] 1003 | name = "linux-raw-sys" 1004 | version = "0.4.15" 1005 | source = "registry+https://github.com/rust-lang/crates.io-index" 1006 | checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" 1007 | 1008 | [[package]] 1009 | name = "litemap" 1010 | version = "0.7.4" 1011 | source = "registry+https://github.com/rust-lang/crates.io-index" 1012 | checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" 1013 | 1014 | [[package]] 1015 | name = "lock_api" 1016 | version = "0.4.12" 1017 | source = "registry+https://github.com/rust-lang/crates.io-index" 1018 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 1019 | dependencies = [ 1020 | "autocfg", 1021 | "scopeguard", 1022 | ] 1023 | 1024 | [[package]] 1025 | name = "log" 1026 | version = "0.4.25" 1027 | source = "registry+https://github.com/rust-lang/crates.io-index" 1028 | checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" 1029 | 1030 | [[package]] 1031 | name = "matchit" 1032 | version = "0.8.4" 1033 | source = "registry+https://github.com/rust-lang/crates.io-index" 1034 | checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" 1035 | 1036 | [[package]] 1037 | name = "memchr" 1038 | version = "2.7.4" 1039 | source = "registry+https://github.com/rust-lang/crates.io-index" 1040 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 1041 | 1042 | [[package]] 1043 | name = "mime" 1044 | version = "0.3.17" 1045 | source = "registry+https://github.com/rust-lang/crates.io-index" 1046 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 1047 | 1048 | [[package]] 1049 | name = "miniz_oxide" 1050 | version = "0.8.3" 1051 | source = "registry+https://github.com/rust-lang/crates.io-index" 1052 | checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" 1053 | dependencies = [ 1054 | "adler2", 1055 | ] 1056 | 1057 | [[package]] 1058 | name = "mio" 1059 | version = "1.0.3" 1060 | source = "registry+https://github.com/rust-lang/crates.io-index" 1061 | checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" 1062 | dependencies = [ 1063 | "libc", 1064 | "wasi 0.11.0+wasi-snapshot-preview1", 1065 | "windows-sys 0.52.0", 1066 | ] 1067 | 1068 | [[package]] 1069 | name = "native-tls" 1070 | version = "0.2.13" 1071 | source = "registry+https://github.com/rust-lang/crates.io-index" 1072 | checksum = "0dab59f8e050d5df8e4dd87d9206fb6f65a483e20ac9fda365ade4fab353196c" 1073 | dependencies = [ 1074 | "libc", 1075 | "log", 1076 | "openssl", 1077 | "openssl-probe", 1078 | "openssl-sys", 1079 | "schannel", 1080 | "security-framework", 1081 | "security-framework-sys", 1082 | "tempfile", 1083 | ] 1084 | 1085 | [[package]] 1086 | name = "nonempty" 1087 | version = "0.7.0" 1088 | source = "registry+https://github.com/rust-lang/crates.io-index" 1089 | checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" 1090 | 1091 | [[package]] 1092 | name = "ns-keyed-archive" 1093 | version = "0.1.3" 1094 | source = "registry+https://github.com/rust-lang/crates.io-index" 1095 | checksum = "3bdf3b814bc8e07ff58c78487d00f693e655c7238d2bff0df2d6d8957f877af7" 1096 | dependencies = [ 1097 | "nskeyedarchiver_converter", 1098 | "plist", 1099 | ] 1100 | 1101 | [[package]] 1102 | name = "nskeyedarchiver_converter" 1103 | version = "0.1.2" 1104 | source = "registry+https://github.com/rust-lang/crates.io-index" 1105 | checksum = "f237a10fe003123daa55a74b63a0b0a65de1671b2d128711ffe5886891a8f77f" 1106 | dependencies = [ 1107 | "clap", 1108 | "plist", 1109 | "thiserror 2.0.11", 1110 | ] 1111 | 1112 | [[package]] 1113 | name = "num-conv" 1114 | version = "0.1.0" 1115 | source = "registry+https://github.com/rust-lang/crates.io-index" 1116 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 1117 | 1118 | [[package]] 1119 | name = "object" 1120 | version = "0.36.7" 1121 | source = "registry+https://github.com/rust-lang/crates.io-index" 1122 | checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" 1123 | dependencies = [ 1124 | "memchr", 1125 | ] 1126 | 1127 | [[package]] 1128 | name = "once_cell" 1129 | version = "1.20.3" 1130 | source = "registry+https://github.com/rust-lang/crates.io-index" 1131 | checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" 1132 | 1133 | [[package]] 1134 | name = "openssl" 1135 | version = "0.10.70" 1136 | source = "registry+https://github.com/rust-lang/crates.io-index" 1137 | checksum = "61cfb4e166a8bb8c9b55c500bc2308550148ece889be90f609377e58140f42c6" 1138 | dependencies = [ 1139 | "bitflags", 1140 | "cfg-if", 1141 | "foreign-types", 1142 | "libc", 1143 | "once_cell", 1144 | "openssl-macros", 1145 | "openssl-sys", 1146 | ] 1147 | 1148 | [[package]] 1149 | name = "openssl-macros" 1150 | version = "0.1.1" 1151 | source = "registry+https://github.com/rust-lang/crates.io-index" 1152 | checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 1153 | dependencies = [ 1154 | "proc-macro2", 1155 | "quote", 1156 | "syn", 1157 | ] 1158 | 1159 | [[package]] 1160 | name = "openssl-probe" 1161 | version = "0.1.6" 1162 | source = "registry+https://github.com/rust-lang/crates.io-index" 1163 | checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" 1164 | 1165 | [[package]] 1166 | name = "openssl-src" 1167 | version = "300.4.1+3.4.0" 1168 | source = "registry+https://github.com/rust-lang/crates.io-index" 1169 | checksum = "faa4eac4138c62414b5622d1b31c5c304f34b406b013c079c2bbc652fdd6678c" 1170 | dependencies = [ 1171 | "cc", 1172 | ] 1173 | 1174 | [[package]] 1175 | name = "openssl-sys" 1176 | version = "0.9.105" 1177 | source = "registry+https://github.com/rust-lang/crates.io-index" 1178 | checksum = "8b22d5b84be05a8d6947c7cb71f7c849aa0f112acd4bf51c2a7c1c988ac0a9dc" 1179 | dependencies = [ 1180 | "cc", 1181 | "libc", 1182 | "openssl-src", 1183 | "pkg-config", 1184 | "vcpkg", 1185 | ] 1186 | 1187 | [[package]] 1188 | name = "parking_lot" 1189 | version = "0.12.3" 1190 | source = "registry+https://github.com/rust-lang/crates.io-index" 1191 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 1192 | dependencies = [ 1193 | "lock_api", 1194 | "parking_lot_core", 1195 | ] 1196 | 1197 | [[package]] 1198 | name = "parking_lot_core" 1199 | version = "0.9.10" 1200 | source = "registry+https://github.com/rust-lang/crates.io-index" 1201 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 1202 | dependencies = [ 1203 | "cfg-if", 1204 | "libc", 1205 | "redox_syscall", 1206 | "smallvec", 1207 | "windows-targets", 1208 | ] 1209 | 1210 | [[package]] 1211 | name = "percent-encoding" 1212 | version = "2.3.1" 1213 | source = "registry+https://github.com/rust-lang/crates.io-index" 1214 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 1215 | 1216 | [[package]] 1217 | name = "pin-project-lite" 1218 | version = "0.2.16" 1219 | source = "registry+https://github.com/rust-lang/crates.io-index" 1220 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 1221 | 1222 | [[package]] 1223 | name = "pin-utils" 1224 | version = "0.1.0" 1225 | source = "registry+https://github.com/rust-lang/crates.io-index" 1226 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1227 | 1228 | [[package]] 1229 | name = "pkg-config" 1230 | version = "0.3.31" 1231 | source = "registry+https://github.com/rust-lang/crates.io-index" 1232 | checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" 1233 | 1234 | [[package]] 1235 | name = "plist" 1236 | version = "1.7.0" 1237 | source = "registry+https://github.com/rust-lang/crates.io-index" 1238 | checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" 1239 | dependencies = [ 1240 | "base64", 1241 | "indexmap", 1242 | "quick-xml", 1243 | "serde", 1244 | "time", 1245 | ] 1246 | 1247 | [[package]] 1248 | name = "powerfmt" 1249 | version = "0.2.0" 1250 | source = "registry+https://github.com/rust-lang/crates.io-index" 1251 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 1252 | 1253 | [[package]] 1254 | name = "ppv-lite86" 1255 | version = "0.2.20" 1256 | source = "registry+https://github.com/rust-lang/crates.io-index" 1257 | checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" 1258 | dependencies = [ 1259 | "zerocopy 0.7.35", 1260 | ] 1261 | 1262 | [[package]] 1263 | name = "proc-macro2" 1264 | version = "1.0.93" 1265 | source = "registry+https://github.com/rust-lang/crates.io-index" 1266 | checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" 1267 | dependencies = [ 1268 | "unicode-ident", 1269 | ] 1270 | 1271 | [[package]] 1272 | name = "quick-xml" 1273 | version = "0.32.0" 1274 | source = "registry+https://github.com/rust-lang/crates.io-index" 1275 | checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2" 1276 | dependencies = [ 1277 | "memchr", 1278 | ] 1279 | 1280 | [[package]] 1281 | name = "quote" 1282 | version = "1.0.38" 1283 | source = "registry+https://github.com/rust-lang/crates.io-index" 1284 | checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" 1285 | dependencies = [ 1286 | "proc-macro2", 1287 | ] 1288 | 1289 | [[package]] 1290 | name = "rand" 1291 | version = "0.8.5" 1292 | source = "registry+https://github.com/rust-lang/crates.io-index" 1293 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1294 | dependencies = [ 1295 | "libc", 1296 | "rand_chacha 0.3.1", 1297 | "rand_core 0.6.4", 1298 | ] 1299 | 1300 | [[package]] 1301 | name = "rand" 1302 | version = "0.9.0" 1303 | source = "registry+https://github.com/rust-lang/crates.io-index" 1304 | checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" 1305 | dependencies = [ 1306 | "rand_chacha 0.9.0", 1307 | "rand_core 0.9.3", 1308 | "zerocopy 0.8.24", 1309 | ] 1310 | 1311 | [[package]] 1312 | name = "rand_chacha" 1313 | version = "0.3.1" 1314 | source = "registry+https://github.com/rust-lang/crates.io-index" 1315 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1316 | dependencies = [ 1317 | "ppv-lite86", 1318 | "rand_core 0.6.4", 1319 | ] 1320 | 1321 | [[package]] 1322 | name = "rand_chacha" 1323 | version = "0.9.0" 1324 | source = "registry+https://github.com/rust-lang/crates.io-index" 1325 | checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" 1326 | dependencies = [ 1327 | "ppv-lite86", 1328 | "rand_core 0.9.3", 1329 | ] 1330 | 1331 | [[package]] 1332 | name = "rand_core" 1333 | version = "0.6.4" 1334 | source = "registry+https://github.com/rust-lang/crates.io-index" 1335 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1336 | dependencies = [ 1337 | "getrandom 0.2.15", 1338 | ] 1339 | 1340 | [[package]] 1341 | name = "rand_core" 1342 | version = "0.9.3" 1343 | source = "registry+https://github.com/rust-lang/crates.io-index" 1344 | checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" 1345 | dependencies = [ 1346 | "getrandom 0.3.1", 1347 | ] 1348 | 1349 | [[package]] 1350 | name = "redox_syscall" 1351 | version = "0.5.8" 1352 | source = "registry+https://github.com/rust-lang/crates.io-index" 1353 | checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" 1354 | dependencies = [ 1355 | "bitflags", 1356 | ] 1357 | 1358 | [[package]] 1359 | name = "regex" 1360 | version = "1.11.1" 1361 | source = "registry+https://github.com/rust-lang/crates.io-index" 1362 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 1363 | dependencies = [ 1364 | "aho-corasick", 1365 | "memchr", 1366 | "regex-automata", 1367 | "regex-syntax", 1368 | ] 1369 | 1370 | [[package]] 1371 | name = "regex-automata" 1372 | version = "0.4.9" 1373 | source = "registry+https://github.com/rust-lang/crates.io-index" 1374 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 1375 | dependencies = [ 1376 | "aho-corasick", 1377 | "memchr", 1378 | "regex-syntax", 1379 | ] 1380 | 1381 | [[package]] 1382 | name = "regex-syntax" 1383 | version = "0.8.5" 1384 | source = "registry+https://github.com/rust-lang/crates.io-index" 1385 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 1386 | 1387 | [[package]] 1388 | name = "reqwest" 1389 | version = "0.12.12" 1390 | source = "registry+https://github.com/rust-lang/crates.io-index" 1391 | checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" 1392 | dependencies = [ 1393 | "base64", 1394 | "bytes", 1395 | "encoding_rs", 1396 | "futures-channel", 1397 | "futures-core", 1398 | "futures-util", 1399 | "h2", 1400 | "http", 1401 | "http-body", 1402 | "http-body-util", 1403 | "hyper", 1404 | "hyper-rustls", 1405 | "hyper-tls", 1406 | "hyper-util", 1407 | "ipnet", 1408 | "js-sys", 1409 | "log", 1410 | "mime", 1411 | "native-tls", 1412 | "once_cell", 1413 | "percent-encoding", 1414 | "pin-project-lite", 1415 | "rustls-pemfile", 1416 | "serde", 1417 | "serde_json", 1418 | "serde_urlencoded", 1419 | "sync_wrapper", 1420 | "system-configuration", 1421 | "tokio", 1422 | "tokio-native-tls", 1423 | "tower", 1424 | "tower-service", 1425 | "url", 1426 | "wasm-bindgen", 1427 | "wasm-bindgen-futures", 1428 | "web-sys", 1429 | "windows-registry", 1430 | ] 1431 | 1432 | [[package]] 1433 | name = "ring" 1434 | version = "0.17.8" 1435 | source = "registry+https://github.com/rust-lang/crates.io-index" 1436 | checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" 1437 | dependencies = [ 1438 | "cc", 1439 | "cfg-if", 1440 | "getrandom 0.2.15", 1441 | "libc", 1442 | "spin", 1443 | "untrusted", 1444 | "windows-sys 0.52.0", 1445 | ] 1446 | 1447 | [[package]] 1448 | name = "rustc-demangle" 1449 | version = "0.1.24" 1450 | source = "registry+https://github.com/rust-lang/crates.io-index" 1451 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 1452 | 1453 | [[package]] 1454 | name = "rustix" 1455 | version = "0.38.44" 1456 | source = "registry+https://github.com/rust-lang/crates.io-index" 1457 | checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" 1458 | dependencies = [ 1459 | "bitflags", 1460 | "errno", 1461 | "libc", 1462 | "linux-raw-sys", 1463 | "windows-sys 0.59.0", 1464 | ] 1465 | 1466 | [[package]] 1467 | name = "rustls" 1468 | version = "0.23.22" 1469 | source = "registry+https://github.com/rust-lang/crates.io-index" 1470 | checksum = "9fb9263ab4eb695e42321db096e3b8fbd715a59b154d5c88d82db2175b681ba7" 1471 | dependencies = [ 1472 | "once_cell", 1473 | "rustls-pki-types", 1474 | "rustls-webpki", 1475 | "subtle", 1476 | "zeroize", 1477 | ] 1478 | 1479 | [[package]] 1480 | name = "rustls-pemfile" 1481 | version = "2.2.0" 1482 | source = "registry+https://github.com/rust-lang/crates.io-index" 1483 | checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" 1484 | dependencies = [ 1485 | "rustls-pki-types", 1486 | ] 1487 | 1488 | [[package]] 1489 | name = "rustls-pki-types" 1490 | version = "1.11.0" 1491 | source = "registry+https://github.com/rust-lang/crates.io-index" 1492 | checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" 1493 | 1494 | [[package]] 1495 | name = "rustls-webpki" 1496 | version = "0.102.8" 1497 | source = "registry+https://github.com/rust-lang/crates.io-index" 1498 | checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" 1499 | dependencies = [ 1500 | "ring", 1501 | "rustls-pki-types", 1502 | "untrusted", 1503 | ] 1504 | 1505 | [[package]] 1506 | name = "rustversion" 1507 | version = "1.0.19" 1508 | source = "registry+https://github.com/rust-lang/crates.io-index" 1509 | checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" 1510 | 1511 | [[package]] 1512 | name = "ryu" 1513 | version = "1.0.19" 1514 | source = "registry+https://github.com/rust-lang/crates.io-index" 1515 | checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" 1516 | 1517 | [[package]] 1518 | name = "schannel" 1519 | version = "0.1.27" 1520 | source = "registry+https://github.com/rust-lang/crates.io-index" 1521 | checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" 1522 | dependencies = [ 1523 | "windows-sys 0.59.0", 1524 | ] 1525 | 1526 | [[package]] 1527 | name = "scopeguard" 1528 | version = "1.2.0" 1529 | source = "registry+https://github.com/rust-lang/crates.io-index" 1530 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1531 | 1532 | [[package]] 1533 | name = "security-framework" 1534 | version = "2.11.1" 1535 | source = "registry+https://github.com/rust-lang/crates.io-index" 1536 | checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" 1537 | dependencies = [ 1538 | "bitflags", 1539 | "core-foundation", 1540 | "core-foundation-sys", 1541 | "libc", 1542 | "security-framework-sys", 1543 | ] 1544 | 1545 | [[package]] 1546 | name = "security-framework-sys" 1547 | version = "2.14.0" 1548 | source = "registry+https://github.com/rust-lang/crates.io-index" 1549 | checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" 1550 | dependencies = [ 1551 | "core-foundation-sys", 1552 | "libc", 1553 | ] 1554 | 1555 | [[package]] 1556 | name = "serde" 1557 | version = "1.0.217" 1558 | source = "registry+https://github.com/rust-lang/crates.io-index" 1559 | checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" 1560 | dependencies = [ 1561 | "serde_derive", 1562 | ] 1563 | 1564 | [[package]] 1565 | name = "serde_derive" 1566 | version = "1.0.217" 1567 | source = "registry+https://github.com/rust-lang/crates.io-index" 1568 | checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" 1569 | dependencies = [ 1570 | "proc-macro2", 1571 | "quote", 1572 | "syn", 1573 | ] 1574 | 1575 | [[package]] 1576 | name = "serde_json" 1577 | version = "1.0.138" 1578 | source = "registry+https://github.com/rust-lang/crates.io-index" 1579 | checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" 1580 | dependencies = [ 1581 | "itoa", 1582 | "memchr", 1583 | "ryu", 1584 | "serde", 1585 | ] 1586 | 1587 | [[package]] 1588 | name = "serde_path_to_error" 1589 | version = "0.1.16" 1590 | source = "registry+https://github.com/rust-lang/crates.io-index" 1591 | checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" 1592 | dependencies = [ 1593 | "itoa", 1594 | "serde", 1595 | ] 1596 | 1597 | [[package]] 1598 | name = "serde_urlencoded" 1599 | version = "0.7.1" 1600 | source = "registry+https://github.com/rust-lang/crates.io-index" 1601 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1602 | dependencies = [ 1603 | "form_urlencoded", 1604 | "itoa", 1605 | "ryu", 1606 | "serde", 1607 | ] 1608 | 1609 | [[package]] 1610 | name = "sha1" 1611 | version = "0.10.6" 1612 | source = "registry+https://github.com/rust-lang/crates.io-index" 1613 | checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" 1614 | dependencies = [ 1615 | "cfg-if", 1616 | "cpufeatures", 1617 | "digest", 1618 | ] 1619 | 1620 | [[package]] 1621 | name = "sha2" 1622 | version = "0.10.8" 1623 | source = "registry+https://github.com/rust-lang/crates.io-index" 1624 | checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 1625 | dependencies = [ 1626 | "cfg-if", 1627 | "cpufeatures", 1628 | "digest", 1629 | ] 1630 | 1631 | [[package]] 1632 | name = "shlex" 1633 | version = "1.3.0" 1634 | source = "registry+https://github.com/rust-lang/crates.io-index" 1635 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1636 | 1637 | [[package]] 1638 | name = "signal-hook-registry" 1639 | version = "1.4.2" 1640 | source = "registry+https://github.com/rust-lang/crates.io-index" 1641 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 1642 | dependencies = [ 1643 | "libc", 1644 | ] 1645 | 1646 | [[package]] 1647 | name = "slab" 1648 | version = "0.4.9" 1649 | source = "registry+https://github.com/rust-lang/crates.io-index" 1650 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1651 | dependencies = [ 1652 | "autocfg", 1653 | ] 1654 | 1655 | [[package]] 1656 | name = "smallvec" 1657 | version = "1.13.2" 1658 | source = "registry+https://github.com/rust-lang/crates.io-index" 1659 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 1660 | 1661 | [[package]] 1662 | name = "socket2" 1663 | version = "0.5.8" 1664 | source = "registry+https://github.com/rust-lang/crates.io-index" 1665 | checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" 1666 | dependencies = [ 1667 | "libc", 1668 | "windows-sys 0.52.0", 1669 | ] 1670 | 1671 | [[package]] 1672 | name = "spin" 1673 | version = "0.9.8" 1674 | source = "registry+https://github.com/rust-lang/crates.io-index" 1675 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 1676 | 1677 | [[package]] 1678 | name = "sqlite" 1679 | version = "0.36.1" 1680 | source = "registry+https://github.com/rust-lang/crates.io-index" 1681 | checksum = "5dfe6fb16f2bee6452feeb4d12bfa404fbcd3cfc121b2950e501d1ae9cae718e" 1682 | dependencies = [ 1683 | "sqlite3-sys", 1684 | ] 1685 | 1686 | [[package]] 1687 | name = "sqlite3-src" 1688 | version = "0.6.1" 1689 | source = "registry+https://github.com/rust-lang/crates.io-index" 1690 | checksum = "174d4a6df77c27db281fb23de1a6d968f3aaaa4807c2a1afa8056b971f947b4a" 1691 | dependencies = [ 1692 | "cc", 1693 | "pkg-config", 1694 | ] 1695 | 1696 | [[package]] 1697 | name = "sqlite3-sys" 1698 | version = "0.17.0" 1699 | source = "registry+https://github.com/rust-lang/crates.io-index" 1700 | checksum = "3901ada7090c3c3584dc92ec7ef1b7091868d13bfe6d7de9f0bcaffee7d0ade5" 1701 | dependencies = [ 1702 | "sqlite3-src", 1703 | ] 1704 | 1705 | [[package]] 1706 | name = "stable_deref_trait" 1707 | version = "1.2.0" 1708 | source = "registry+https://github.com/rust-lang/crates.io-index" 1709 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1710 | 1711 | [[package]] 1712 | name = "strsim" 1713 | version = "0.11.1" 1714 | source = "registry+https://github.com/rust-lang/crates.io-index" 1715 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1716 | 1717 | [[package]] 1718 | name = "subtle" 1719 | version = "2.6.1" 1720 | source = "registry+https://github.com/rust-lang/crates.io-index" 1721 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 1722 | 1723 | [[package]] 1724 | name = "syn" 1725 | version = "2.0.98" 1726 | source = "registry+https://github.com/rust-lang/crates.io-index" 1727 | checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" 1728 | dependencies = [ 1729 | "proc-macro2", 1730 | "quote", 1731 | "unicode-ident", 1732 | ] 1733 | 1734 | [[package]] 1735 | name = "sync_wrapper" 1736 | version = "1.0.2" 1737 | source = "registry+https://github.com/rust-lang/crates.io-index" 1738 | checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" 1739 | dependencies = [ 1740 | "futures-core", 1741 | ] 1742 | 1743 | [[package]] 1744 | name = "synstructure" 1745 | version = "0.13.1" 1746 | source = "registry+https://github.com/rust-lang/crates.io-index" 1747 | checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" 1748 | dependencies = [ 1749 | "proc-macro2", 1750 | "quote", 1751 | "syn", 1752 | ] 1753 | 1754 | [[package]] 1755 | name = "system-configuration" 1756 | version = "0.6.1" 1757 | source = "registry+https://github.com/rust-lang/crates.io-index" 1758 | checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" 1759 | dependencies = [ 1760 | "bitflags", 1761 | "core-foundation", 1762 | "system-configuration-sys", 1763 | ] 1764 | 1765 | [[package]] 1766 | name = "system-configuration-sys" 1767 | version = "0.6.0" 1768 | source = "registry+https://github.com/rust-lang/crates.io-index" 1769 | checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" 1770 | dependencies = [ 1771 | "core-foundation-sys", 1772 | "libc", 1773 | ] 1774 | 1775 | [[package]] 1776 | name = "tempfile" 1777 | version = "3.16.0" 1778 | source = "registry+https://github.com/rust-lang/crates.io-index" 1779 | checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" 1780 | dependencies = [ 1781 | "cfg-if", 1782 | "fastrand", 1783 | "getrandom 0.3.1", 1784 | "once_cell", 1785 | "rustix", 1786 | "windows-sys 0.59.0", 1787 | ] 1788 | 1789 | [[package]] 1790 | name = "thiserror" 1791 | version = "1.0.69" 1792 | source = "registry+https://github.com/rust-lang/crates.io-index" 1793 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 1794 | dependencies = [ 1795 | "thiserror-impl 1.0.69", 1796 | ] 1797 | 1798 | [[package]] 1799 | name = "thiserror" 1800 | version = "2.0.11" 1801 | source = "registry+https://github.com/rust-lang/crates.io-index" 1802 | checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" 1803 | dependencies = [ 1804 | "thiserror-impl 2.0.11", 1805 | ] 1806 | 1807 | [[package]] 1808 | name = "thiserror-impl" 1809 | version = "1.0.69" 1810 | source = "registry+https://github.com/rust-lang/crates.io-index" 1811 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 1812 | dependencies = [ 1813 | "proc-macro2", 1814 | "quote", 1815 | "syn", 1816 | ] 1817 | 1818 | [[package]] 1819 | name = "thiserror-impl" 1820 | version = "2.0.11" 1821 | source = "registry+https://github.com/rust-lang/crates.io-index" 1822 | checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" 1823 | dependencies = [ 1824 | "proc-macro2", 1825 | "quote", 1826 | "syn", 1827 | ] 1828 | 1829 | [[package]] 1830 | name = "time" 1831 | version = "0.3.37" 1832 | source = "registry+https://github.com/rust-lang/crates.io-index" 1833 | checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" 1834 | dependencies = [ 1835 | "deranged", 1836 | "itoa", 1837 | "num-conv", 1838 | "powerfmt", 1839 | "serde", 1840 | "time-core", 1841 | "time-macros", 1842 | ] 1843 | 1844 | [[package]] 1845 | name = "time-core" 1846 | version = "0.1.2" 1847 | source = "registry+https://github.com/rust-lang/crates.io-index" 1848 | checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" 1849 | 1850 | [[package]] 1851 | name = "time-macros" 1852 | version = "0.2.19" 1853 | source = "registry+https://github.com/rust-lang/crates.io-index" 1854 | checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" 1855 | dependencies = [ 1856 | "num-conv", 1857 | "time-core", 1858 | ] 1859 | 1860 | [[package]] 1861 | name = "tinystr" 1862 | version = "0.7.6" 1863 | source = "registry+https://github.com/rust-lang/crates.io-index" 1864 | checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" 1865 | dependencies = [ 1866 | "displaydoc", 1867 | "zerovec", 1868 | ] 1869 | 1870 | [[package]] 1871 | name = "tokio" 1872 | version = "1.43.0" 1873 | source = "registry+https://github.com/rust-lang/crates.io-index" 1874 | checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" 1875 | dependencies = [ 1876 | "backtrace", 1877 | "bytes", 1878 | "libc", 1879 | "mio", 1880 | "parking_lot", 1881 | "pin-project-lite", 1882 | "signal-hook-registry", 1883 | "socket2", 1884 | "tokio-macros", 1885 | "windows-sys 0.52.0", 1886 | ] 1887 | 1888 | [[package]] 1889 | name = "tokio-macros" 1890 | version = "2.5.0" 1891 | source = "registry+https://github.com/rust-lang/crates.io-index" 1892 | checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" 1893 | dependencies = [ 1894 | "proc-macro2", 1895 | "quote", 1896 | "syn", 1897 | ] 1898 | 1899 | [[package]] 1900 | name = "tokio-native-tls" 1901 | version = "0.3.1" 1902 | source = "registry+https://github.com/rust-lang/crates.io-index" 1903 | checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" 1904 | dependencies = [ 1905 | "native-tls", 1906 | "tokio", 1907 | ] 1908 | 1909 | [[package]] 1910 | name = "tokio-openssl" 1911 | version = "0.6.5" 1912 | source = "registry+https://github.com/rust-lang/crates.io-index" 1913 | checksum = "59df6849caa43bb7567f9a36f863c447d95a11d5903c9cc334ba32576a27eadd" 1914 | dependencies = [ 1915 | "openssl", 1916 | "openssl-sys", 1917 | "tokio", 1918 | ] 1919 | 1920 | [[package]] 1921 | name = "tokio-rustls" 1922 | version = "0.26.1" 1923 | source = "registry+https://github.com/rust-lang/crates.io-index" 1924 | checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" 1925 | dependencies = [ 1926 | "rustls", 1927 | "tokio", 1928 | ] 1929 | 1930 | [[package]] 1931 | name = "tokio-tungstenite" 1932 | version = "0.26.1" 1933 | source = "registry+https://github.com/rust-lang/crates.io-index" 1934 | checksum = "be4bf6fecd69fcdede0ec680aaf474cdab988f9de6bc73d3758f0160e3b7025a" 1935 | dependencies = [ 1936 | "futures-util", 1937 | "log", 1938 | "tokio", 1939 | "tungstenite", 1940 | ] 1941 | 1942 | [[package]] 1943 | name = "tokio-util" 1944 | version = "0.7.13" 1945 | source = "registry+https://github.com/rust-lang/crates.io-index" 1946 | checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" 1947 | dependencies = [ 1948 | "bytes", 1949 | "futures-core", 1950 | "futures-sink", 1951 | "pin-project-lite", 1952 | "tokio", 1953 | ] 1954 | 1955 | [[package]] 1956 | name = "tower" 1957 | version = "0.5.2" 1958 | source = "registry+https://github.com/rust-lang/crates.io-index" 1959 | checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" 1960 | dependencies = [ 1961 | "futures-core", 1962 | "futures-util", 1963 | "pin-project-lite", 1964 | "sync_wrapper", 1965 | "tokio", 1966 | "tower-layer", 1967 | "tower-service", 1968 | "tracing", 1969 | ] 1970 | 1971 | [[package]] 1972 | name = "tower-http" 1973 | version = "0.6.2" 1974 | source = "registry+https://github.com/rust-lang/crates.io-index" 1975 | checksum = "403fa3b783d4b626a8ad51d766ab03cb6d2dbfc46b1c5d4448395e6628dc9697" 1976 | dependencies = [ 1977 | "bitflags", 1978 | "bytes", 1979 | "http", 1980 | "pin-project-lite", 1981 | "tower-layer", 1982 | "tower-service", 1983 | ] 1984 | 1985 | [[package]] 1986 | name = "tower-layer" 1987 | version = "0.3.3" 1988 | source = "registry+https://github.com/rust-lang/crates.io-index" 1989 | checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" 1990 | 1991 | [[package]] 1992 | name = "tower-service" 1993 | version = "0.3.3" 1994 | source = "registry+https://github.com/rust-lang/crates.io-index" 1995 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 1996 | 1997 | [[package]] 1998 | name = "tracing" 1999 | version = "0.1.41" 2000 | source = "registry+https://github.com/rust-lang/crates.io-index" 2001 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 2002 | dependencies = [ 2003 | "log", 2004 | "pin-project-lite", 2005 | "tracing-core", 2006 | ] 2007 | 2008 | [[package]] 2009 | name = "tracing-core" 2010 | version = "0.1.33" 2011 | source = "registry+https://github.com/rust-lang/crates.io-index" 2012 | checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" 2013 | dependencies = [ 2014 | "once_cell", 2015 | ] 2016 | 2017 | [[package]] 2018 | name = "try-lock" 2019 | version = "0.2.5" 2020 | source = "registry+https://github.com/rust-lang/crates.io-index" 2021 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 2022 | 2023 | [[package]] 2024 | name = "tungstenite" 2025 | version = "0.26.1" 2026 | source = "registry+https://github.com/rust-lang/crates.io-index" 2027 | checksum = "413083a99c579593656008130e29255e54dcaae495be556cc26888f211648c24" 2028 | dependencies = [ 2029 | "byteorder", 2030 | "bytes", 2031 | "data-encoding", 2032 | "http", 2033 | "httparse", 2034 | "log", 2035 | "rand 0.8.5", 2036 | "sha1", 2037 | "thiserror 2.0.11", 2038 | "utf-8", 2039 | ] 2040 | 2041 | [[package]] 2042 | name = "typenum" 2043 | version = "1.17.0" 2044 | source = "registry+https://github.com/rust-lang/crates.io-index" 2045 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 2046 | 2047 | [[package]] 2048 | name = "unicode-ident" 2049 | version = "1.0.16" 2050 | source = "registry+https://github.com/rust-lang/crates.io-index" 2051 | checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" 2052 | 2053 | [[package]] 2054 | name = "untrusted" 2055 | version = "0.9.0" 2056 | source = "registry+https://github.com/rust-lang/crates.io-index" 2057 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 2058 | 2059 | [[package]] 2060 | name = "url" 2061 | version = "2.5.4" 2062 | source = "registry+https://github.com/rust-lang/crates.io-index" 2063 | checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" 2064 | dependencies = [ 2065 | "form_urlencoded", 2066 | "idna", 2067 | "percent-encoding", 2068 | ] 2069 | 2070 | [[package]] 2071 | name = "utf-8" 2072 | version = "0.7.6" 2073 | source = "registry+https://github.com/rust-lang/crates.io-index" 2074 | checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 2075 | 2076 | [[package]] 2077 | name = "utf16_iter" 2078 | version = "1.0.5" 2079 | source = "registry+https://github.com/rust-lang/crates.io-index" 2080 | checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" 2081 | 2082 | [[package]] 2083 | name = "utf8_iter" 2084 | version = "1.0.4" 2085 | source = "registry+https://github.com/rust-lang/crates.io-index" 2086 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 2087 | 2088 | [[package]] 2089 | name = "utf8parse" 2090 | version = "0.2.2" 2091 | source = "registry+https://github.com/rust-lang/crates.io-index" 2092 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 2093 | 2094 | [[package]] 2095 | name = "uuid" 2096 | version = "1.13.1" 2097 | source = "registry+https://github.com/rust-lang/crates.io-index" 2098 | checksum = "ced87ca4be083373936a67f8de945faa23b6b42384bd5b64434850802c6dccd0" 2099 | dependencies = [ 2100 | "getrandom 0.3.1", 2101 | "serde", 2102 | ] 2103 | 2104 | [[package]] 2105 | name = "vcpkg" 2106 | version = "0.2.15" 2107 | source = "registry+https://github.com/rust-lang/crates.io-index" 2108 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 2109 | 2110 | [[package]] 2111 | name = "version_check" 2112 | version = "0.9.5" 2113 | source = "registry+https://github.com/rust-lang/crates.io-index" 2114 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 2115 | 2116 | [[package]] 2117 | name = "want" 2118 | version = "0.3.1" 2119 | source = "registry+https://github.com/rust-lang/crates.io-index" 2120 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 2121 | dependencies = [ 2122 | "try-lock", 2123 | ] 2124 | 2125 | [[package]] 2126 | name = "wasi" 2127 | version = "0.11.0+wasi-snapshot-preview1" 2128 | source = "registry+https://github.com/rust-lang/crates.io-index" 2129 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 2130 | 2131 | [[package]] 2132 | name = "wasi" 2133 | version = "0.13.3+wasi-0.2.2" 2134 | source = "registry+https://github.com/rust-lang/crates.io-index" 2135 | checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" 2136 | dependencies = [ 2137 | "wit-bindgen-rt", 2138 | ] 2139 | 2140 | [[package]] 2141 | name = "wasm-bindgen" 2142 | version = "0.2.100" 2143 | source = "registry+https://github.com/rust-lang/crates.io-index" 2144 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 2145 | dependencies = [ 2146 | "cfg-if", 2147 | "once_cell", 2148 | "rustversion", 2149 | "wasm-bindgen-macro", 2150 | ] 2151 | 2152 | [[package]] 2153 | name = "wasm-bindgen-backend" 2154 | version = "0.2.100" 2155 | source = "registry+https://github.com/rust-lang/crates.io-index" 2156 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 2157 | dependencies = [ 2158 | "bumpalo", 2159 | "log", 2160 | "proc-macro2", 2161 | "quote", 2162 | "syn", 2163 | "wasm-bindgen-shared", 2164 | ] 2165 | 2166 | [[package]] 2167 | name = "wasm-bindgen-futures" 2168 | version = "0.4.50" 2169 | source = "registry+https://github.com/rust-lang/crates.io-index" 2170 | checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" 2171 | dependencies = [ 2172 | "cfg-if", 2173 | "js-sys", 2174 | "once_cell", 2175 | "wasm-bindgen", 2176 | "web-sys", 2177 | ] 2178 | 2179 | [[package]] 2180 | name = "wasm-bindgen-macro" 2181 | version = "0.2.100" 2182 | source = "registry+https://github.com/rust-lang/crates.io-index" 2183 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 2184 | dependencies = [ 2185 | "quote", 2186 | "wasm-bindgen-macro-support", 2187 | ] 2188 | 2189 | [[package]] 2190 | name = "wasm-bindgen-macro-support" 2191 | version = "0.2.100" 2192 | source = "registry+https://github.com/rust-lang/crates.io-index" 2193 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 2194 | dependencies = [ 2195 | "proc-macro2", 2196 | "quote", 2197 | "syn", 2198 | "wasm-bindgen-backend", 2199 | "wasm-bindgen-shared", 2200 | ] 2201 | 2202 | [[package]] 2203 | name = "wasm-bindgen-shared" 2204 | version = "0.2.100" 2205 | source = "registry+https://github.com/rust-lang/crates.io-index" 2206 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 2207 | dependencies = [ 2208 | "unicode-ident", 2209 | ] 2210 | 2211 | [[package]] 2212 | name = "web-sys" 2213 | version = "0.3.77" 2214 | source = "registry+https://github.com/rust-lang/crates.io-index" 2215 | checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" 2216 | dependencies = [ 2217 | "js-sys", 2218 | "wasm-bindgen", 2219 | ] 2220 | 2221 | [[package]] 2222 | name = "wg-config" 2223 | version = "0.0.7" 2224 | source = "git+https://github.com/jkcoxson/wg-config#453cae2e30458bf4c0ecdb0e06f19fa137e4ce70" 2225 | dependencies = [ 2226 | "base64", 2227 | "ipnetwork", 2228 | ] 2229 | 2230 | [[package]] 2231 | name = "windows-registry" 2232 | version = "0.2.0" 2233 | source = "registry+https://github.com/rust-lang/crates.io-index" 2234 | checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" 2235 | dependencies = [ 2236 | "windows-result", 2237 | "windows-strings", 2238 | "windows-targets", 2239 | ] 2240 | 2241 | [[package]] 2242 | name = "windows-result" 2243 | version = "0.2.0" 2244 | source = "registry+https://github.com/rust-lang/crates.io-index" 2245 | checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" 2246 | dependencies = [ 2247 | "windows-targets", 2248 | ] 2249 | 2250 | [[package]] 2251 | name = "windows-strings" 2252 | version = "0.1.0" 2253 | source = "registry+https://github.com/rust-lang/crates.io-index" 2254 | checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" 2255 | dependencies = [ 2256 | "windows-result", 2257 | "windows-targets", 2258 | ] 2259 | 2260 | [[package]] 2261 | name = "windows-sys" 2262 | version = "0.52.0" 2263 | source = "registry+https://github.com/rust-lang/crates.io-index" 2264 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 2265 | dependencies = [ 2266 | "windows-targets", 2267 | ] 2268 | 2269 | [[package]] 2270 | name = "windows-sys" 2271 | version = "0.59.0" 2272 | source = "registry+https://github.com/rust-lang/crates.io-index" 2273 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 2274 | dependencies = [ 2275 | "windows-targets", 2276 | ] 2277 | 2278 | [[package]] 2279 | name = "windows-targets" 2280 | version = "0.52.6" 2281 | source = "registry+https://github.com/rust-lang/crates.io-index" 2282 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 2283 | dependencies = [ 2284 | "windows_aarch64_gnullvm", 2285 | "windows_aarch64_msvc", 2286 | "windows_i686_gnu", 2287 | "windows_i686_gnullvm", 2288 | "windows_i686_msvc", 2289 | "windows_x86_64_gnu", 2290 | "windows_x86_64_gnullvm", 2291 | "windows_x86_64_msvc", 2292 | ] 2293 | 2294 | [[package]] 2295 | name = "windows_aarch64_gnullvm" 2296 | version = "0.52.6" 2297 | source = "registry+https://github.com/rust-lang/crates.io-index" 2298 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 2299 | 2300 | [[package]] 2301 | name = "windows_aarch64_msvc" 2302 | version = "0.52.6" 2303 | source = "registry+https://github.com/rust-lang/crates.io-index" 2304 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 2305 | 2306 | [[package]] 2307 | name = "windows_i686_gnu" 2308 | version = "0.52.6" 2309 | source = "registry+https://github.com/rust-lang/crates.io-index" 2310 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 2311 | 2312 | [[package]] 2313 | name = "windows_i686_gnullvm" 2314 | version = "0.52.6" 2315 | source = "registry+https://github.com/rust-lang/crates.io-index" 2316 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 2317 | 2318 | [[package]] 2319 | name = "windows_i686_msvc" 2320 | version = "0.52.6" 2321 | source = "registry+https://github.com/rust-lang/crates.io-index" 2322 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 2323 | 2324 | [[package]] 2325 | name = "windows_x86_64_gnu" 2326 | version = "0.52.6" 2327 | source = "registry+https://github.com/rust-lang/crates.io-index" 2328 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 2329 | 2330 | [[package]] 2331 | name = "windows_x86_64_gnullvm" 2332 | version = "0.52.6" 2333 | source = "registry+https://github.com/rust-lang/crates.io-index" 2334 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 2335 | 2336 | [[package]] 2337 | name = "windows_x86_64_msvc" 2338 | version = "0.52.6" 2339 | source = "registry+https://github.com/rust-lang/crates.io-index" 2340 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 2341 | 2342 | [[package]] 2343 | name = "wit-bindgen-rt" 2344 | version = "0.33.0" 2345 | source = "registry+https://github.com/rust-lang/crates.io-index" 2346 | checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" 2347 | dependencies = [ 2348 | "bitflags", 2349 | ] 2350 | 2351 | [[package]] 2352 | name = "write16" 2353 | version = "1.0.0" 2354 | source = "registry+https://github.com/rust-lang/crates.io-index" 2355 | checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" 2356 | 2357 | [[package]] 2358 | name = "writeable" 2359 | version = "0.5.5" 2360 | source = "registry+https://github.com/rust-lang/crates.io-index" 2361 | checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" 2362 | 2363 | [[package]] 2364 | name = "yoke" 2365 | version = "0.7.5" 2366 | source = "registry+https://github.com/rust-lang/crates.io-index" 2367 | checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" 2368 | dependencies = [ 2369 | "serde", 2370 | "stable_deref_trait", 2371 | "yoke-derive", 2372 | "zerofrom", 2373 | ] 2374 | 2375 | [[package]] 2376 | name = "yoke-derive" 2377 | version = "0.7.5" 2378 | source = "registry+https://github.com/rust-lang/crates.io-index" 2379 | checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" 2380 | dependencies = [ 2381 | "proc-macro2", 2382 | "quote", 2383 | "syn", 2384 | "synstructure", 2385 | ] 2386 | 2387 | [[package]] 2388 | name = "zerocopy" 2389 | version = "0.7.35" 2390 | source = "registry+https://github.com/rust-lang/crates.io-index" 2391 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 2392 | dependencies = [ 2393 | "byteorder", 2394 | "zerocopy-derive 0.7.35", 2395 | ] 2396 | 2397 | [[package]] 2398 | name = "zerocopy" 2399 | version = "0.8.24" 2400 | source = "registry+https://github.com/rust-lang/crates.io-index" 2401 | checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" 2402 | dependencies = [ 2403 | "zerocopy-derive 0.8.24", 2404 | ] 2405 | 2406 | [[package]] 2407 | name = "zerocopy-derive" 2408 | version = "0.7.35" 2409 | source = "registry+https://github.com/rust-lang/crates.io-index" 2410 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 2411 | dependencies = [ 2412 | "proc-macro2", 2413 | "quote", 2414 | "syn", 2415 | ] 2416 | 2417 | [[package]] 2418 | name = "zerocopy-derive" 2419 | version = "0.8.24" 2420 | source = "registry+https://github.com/rust-lang/crates.io-index" 2421 | checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" 2422 | dependencies = [ 2423 | "proc-macro2", 2424 | "quote", 2425 | "syn", 2426 | ] 2427 | 2428 | [[package]] 2429 | name = "zerofrom" 2430 | version = "0.1.5" 2431 | source = "registry+https://github.com/rust-lang/crates.io-index" 2432 | checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" 2433 | dependencies = [ 2434 | "zerofrom-derive", 2435 | ] 2436 | 2437 | [[package]] 2438 | name = "zerofrom-derive" 2439 | version = "0.1.5" 2440 | source = "registry+https://github.com/rust-lang/crates.io-index" 2441 | checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" 2442 | dependencies = [ 2443 | "proc-macro2", 2444 | "quote", 2445 | "syn", 2446 | "synstructure", 2447 | ] 2448 | 2449 | [[package]] 2450 | name = "zeroize" 2451 | version = "1.8.1" 2452 | source = "registry+https://github.com/rust-lang/crates.io-index" 2453 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 2454 | 2455 | [[package]] 2456 | name = "zerovec" 2457 | version = "0.10.4" 2458 | source = "registry+https://github.com/rust-lang/crates.io-index" 2459 | checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" 2460 | dependencies = [ 2461 | "yoke", 2462 | "zerofrom", 2463 | "zerovec-derive", 2464 | ] 2465 | 2466 | [[package]] 2467 | name = "zerovec-derive" 2468 | version = "0.10.3" 2469 | source = "registry+https://github.com/rust-lang/crates.io-index" 2470 | checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" 2471 | dependencies = [ 2472 | "proc-macro2", 2473 | "quote", 2474 | "syn", 2475 | ] 2476 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jitstreamer-eb" 3 | version = "0.1.1" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | tokio = { version = "1.43", features = ["full"] } 8 | axum = { version = "0.8", features = ["json", "macros", "ws"] } 9 | tower-http = { version = "0.6", features = ["cors"] } 10 | axum-macros = { version = "0.5" } 11 | axum-client-ip = { version = "0.7" } 12 | serde = { version = "1.0", features = ["derive"] } 13 | serde_json = { version = "1.0" } 14 | env_logger = { version = "0.11" } 15 | log = { version = "0.4" } 16 | idevice = { version = "0.1.26", features = [ 17 | "core_device_proxy", 18 | "heartbeat", 19 | "mounter", 20 | "tcp", 21 | "installation_proxy", 22 | "tss", 23 | "dvt", 24 | "tunnel_tcp_stack", 25 | "xpc", 26 | "debug_proxy", 27 | ] } 28 | plist = { version = "1.7" } 29 | sqlite = { version = "0.36" } 30 | wg-config = { git = "https://github.com/jkcoxson/wg-config" } 31 | bytes = { version = "1.9" } 32 | sha2 = { version = "0.10" } 33 | dotenvy = { version = "0.15" } 34 | reqwest = { version = "0.12", features = ["json"] } 35 | 36 | [build-dependencies] 37 | reqwest = { version = "0.12", features = ["blocking"] } 38 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # License: Dual License (AGPL for personal use, Paid License for commercial use) 2 | 3 | ## Option 1: Free for Personal & Open-Source Use (GNU AGPLv3) 4 | 5 | This software is licensed under the **GNU Affero General Public License v3 (AGPL-3.0)** for non-commercial and open-source use. 6 | 7 | You may: 8 | - Use, modify, and distribute the software **for personal, educational, or open-source projects**. 9 | - Contribute to the project under AGPLv3. 10 | 11 | However, **if you use this software in a commercial setting, you must open-source your modifications and comply with AGPL-3.0.** 12 | 13 | Full AGPLv3 license: https://www.gnu.org/licenses/agpl-3.0.html 14 | 15 | --- 16 | 17 | ## Option 2: Commercial Use Requires a Paid License 18 | For commercial use (e.g., businesses, SaaS, proprietary software), a separate **paid commercial license** is required. 19 | 20 | Commercial users **must purchase a license** to: 21 | - Use the software in proprietary or closed-source applications. 22 | - Avoid AGPL’s open-source requirements. 23 | 24 | To obtain a commercial license, contact: 25 | 📧 sales (at) jkcoxson.com 26 | 🌐 jkcoxson.com 27 | 28 | --- 29 | 30 | ## Summary 31 | 32 | ✔ **Personal, educational, and open-source use** → Free under AGPLv3 33 | 💰 **Business, SaaS, or closed-source use** → Requires a paid license 34 | 35 | For any questions or licensing inquiries, feel free to reach out! 36 | 37 | Copyright (c) 2025 Jackson Coxson 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JitStreamer EB 2 | 3 | The sequel that nobody wanted, but everyone needed. 4 | 5 | JitStreamer is a program to activate JIT across the far reaches of the internet. 6 | 7 | I authored the original JitStreamer a few years ago, but Apple has since changed 8 | how the protocol for debugging apps works. This program is a rewrite of that original 9 | program, while using the new protocol. 10 | 11 | Simply put, this program takes a pairing file and returns a Wireguard configuration. 12 | That Wireguard configuration allows the device to interact with a server that will 13 | activate JIT on the device. 14 | 15 | ## EB 16 | 17 | What is EB? Electric Boogaloo. 18 | [r/outoftheloop](https://www.reddit.com/r/OutOfTheLoop/comments/3o41fi/where_does_the_name_of_something2_electric/) 19 | 20 | ## Building 21 | 22 | ```bash 23 | cargo build --release 24 | 25 | ``` 26 | 27 | It's not that deep. 28 | 29 | ## Running 30 | 31 | 1. Start [netmuxd](https://github.com/jkcoxson/netmuxd) 32 | 2. Install the pip requirements 33 | 34 | ```bash 35 | pip install -r requirements.txt 36 | ``` 37 | 38 | 3. Start [tunneld-rs](https://github.com/jkcoxson/tunneld-rs) or [tunneld](https://github.com/doronz88/pymobiledevice3) 39 | 40 | 4. Run the program 41 | 42 | ```bash 43 | ./target/release/jitstreamer-eb 44 | ``` 45 | 46 | **OR** 47 | 48 | ```bash 49 | just run 50 | ``` 51 | 52 | 5. Start the Wireguard peer 53 | 54 | ```bash 55 | sudo wg-quick up jitstreamer 56 | ``` 57 | 58 | 6. ??? 59 | 7. Profit 60 | 61 | ### Variables 62 | 63 | JitStreamer reads the following environment variables: 64 | 65 | - ``RUNNER_COUNT`` - How many Python runners to spawn, defaults to ``5`` 66 | - ``ALLOW_REGISTRATION`` - Allows clients to register using the ``/register`` endpoint, defaults to ``1``. Set to 2 to register using client's address instead of generating wireguard address 67 | - ``JITSTREAMER_PORT`` - The port to bind to, defaults to ``9172`` 68 | - ``WIREGUARD_CONFIG_NAME`` - The name of the Wireguard interface, defaults to ``jitstreamer`` 69 | - ``WIREGUARD_PORT`` - The port that Wireguard listens on, defaults to ``51869`` 70 | - ``WIREGUARD_SERVER_ADDRESS`` - The address the server binds to, defaults to ``fd00::`` 71 | - ``WIREGUARD_ENDPOINT`` - The endpoint that client configs point to, defaults to ``jitstreamer.jkcoxson.com`` 72 | - ``WIREGUARD_SERVER_ALLOWED_IPS`` - The allowed IPs the server can bind to, defaults to ``fd00::/64`` 73 | 74 | ### Custom VPN 75 | 76 | If you don't want to use the built-in Wireguard manager, because you either 77 | have your own VPN or want to use a different one, you'll have to manually 78 | register your clients. 79 | 80 | Run the following SQL on the ``jitstreamer.db`` sqlite file: 81 | 82 | ```sql 83 | INSERT INTO DEVICES (udid, ip, last_used) VALUES ([udid], [ip], CURRENT_TIMESTAMP); 84 | ``` 85 | 86 | ## Docker 87 | 88 | There's a nice dockerfile that contains a Wireguard server and JitStreamer server, 89 | all packaged and ready to go. It contains everything you need to run the server. 90 | 91 | 1. create a database 92 | 93 | ```bash 94 | mkdir app 95 | sqlite3 ./jitstreamer.db < ./src/sql/up.sql 96 | ``` 97 | 98 | 2. build docker 99 | 100 | ```bash 101 | sudo docker build -t jitstreamer-eb . 102 | ``` 103 | 104 | 3. run docker compose 105 | 106 | ```bash 107 | sudo docker compose up -d 108 | ``` 109 | 110 | Alternative method: 111 | 112 | ```bash 113 | just docker-build 114 | just docker-run 115 | ``` 116 | 117 | Detailed Step by Step Docker Compose [Guide](https://github.com/jkcoxson/JitStreamer-EB/blob/master/install-docs/jitstreamer-eb-debian-docker-instructions.md) 118 | 119 | There is also a script that uses combines the commands from the Step by Step Docker Compose Guide, the steps to use it follow. 120 | IMPORTANT: THIS WILL ONLY WORK ON UBUNTU/DEBIAN!!! 121 | 122 | 1. clone the repo onto your home directory 123 | 124 | ```bash 125 | sudo apt install git-all 126 | git clone 127 | ``` 128 | 129 | 2. go into the directory and run the script 130 | 131 | ```bash 132 | cd JitStreamer-EB/ 133 | bash jitstreamer.sh 134 | ``` 135 | 136 | 3. follow the instructions provided by the script 137 | - use a pairing file you created on another pc (preferred), 138 | or create one through following this guide (currently not working in script :( *todo) (https://github.com/osy/Jitterbug) 139 | - then you need to find the IP of your iPhone, you can see this through settings - wifi - i icon - ip address 140 | 141 | 4. if this docker container stops, you can start it up again in your home directory through 142 | 143 | ```bash 144 | cd /JitStreamer-EB 145 | sudo docker compose up -d 146 | ``` 147 | 148 | ## Additional methods of installation 149 | 150 | [Click this](https://github.com/jkcoxson/JitStreamer-EB/blob/master/install-docs) 151 | 152 | ## License 153 | 154 | [LICENSE.md] 155 | 156 | ## Contributing 157 | 158 | Please do. Pull requests will be accepted after passing cargo clippy. 159 | 160 | ## Thanks 161 | 162 | - [ny](https://github.com/nythepegasus/SideJITServer) for the Python implementation 163 | - [pymobiledevice3](https://github.com/doronz88/pymobiledevice3) 164 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | // Jackson Coxson 2 | 3 | use reqwest::blocking::get; 4 | use std::fs; 5 | use std::path::Path; 6 | 7 | const URLS: [&str; 3] = ["https://github.com/doronz88/DeveloperDiskImage/raw/refs/heads/main/PersonalizedImages/Xcode_iOS_DDI_Personalized/BuildManifest.plist", "https://github.com/doronz88/DeveloperDiskImage/raw/refs/heads/main/PersonalizedImages/Xcode_iOS_DDI_Personalized/Image.dmg", "https://github.com/doronz88/DeveloperDiskImage/raw/refs/heads/main/PersonalizedImages/Xcode_iOS_DDI_Personalized/Image.dmg.trustcache"]; 8 | const OUTPUT_DIR: &str = "DDI"; 9 | const OUTPUT_FILES: [&str; 3] = [ 10 | "DDI/BuildManifest.plist", 11 | "DDI/Image.dmg", 12 | "DDI/Image.dmg.trustcache", 13 | ]; 14 | 15 | fn main() { 16 | println!("cargo:rerun-if-changed=build.rs"); 17 | 18 | // Ensure output directory exists 19 | if !Path::new(OUTPUT_DIR).exists() { 20 | fs::create_dir_all(OUTPUT_DIR).expect("Failed to create DDI directory"); 21 | } 22 | 23 | // Check if the file already exists 24 | if Path::new(OUTPUT_FILES[0]).exists() { 25 | return; 26 | } 27 | 28 | // Download the file using reqwest 29 | println!("Downloading BuildManifest.plist..."); 30 | for (i, url) in URLS.iter().enumerate() { 31 | let response = get(*url).expect("Failed to send request"); 32 | let bytes = response.bytes().expect("Failed to read response"); 33 | fs::write(OUTPUT_FILES[i], &bytes).expect("Failed to write file"); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | jitstreamer-eb: 3 | container_name: jitstreamer-eb 4 | network_mode: host #TODO, is bridge mode possible? 5 | volumes: 6 | - ./lockdown:/var/lib/lockdown 7 | - ./wireguard:/etc/wireguard 8 | - ./jitstreamer.db:/app/jitstreamer.db 9 | environment: 10 | - RUST_LOG=info 11 | - RUNNER_COUNT=1 12 | cap_add: 13 | - NET_ADMIN 14 | devices: 15 | - /dev/net/tun:/dev/net/tun 16 | image: jkcoxson/jitstreamer-eb:latest 17 | restart: unless-stopped 18 | 19 | -------------------------------------------------------------------------------- /dockerfile: -------------------------------------------------------------------------------- 1 | # Jackson Coxson 2 | # todo finish this 3 | 4 | # Use a base image with Rust for building the project 5 | FROM rust:latest AS builder 6 | 7 | # Set the working directory 8 | WORKDIR /app 9 | 10 | # Copy the project files into the container 11 | COPY . . 12 | 13 | # Build the JitStreamer EB project in release mode 14 | RUN cargo build --release 15 | 16 | # Prepare the final runtime image 17 | FROM debian:bookworm-slim 18 | 19 | # Install required runtime dependencies 20 | RUN apt-get update && apt-get install -y \ 21 | wireguard-tools \ 22 | iproute2 \ 23 | librust-openssl-dev \ 24 | libssl-dev && \ 25 | rm -rf /var/lib/apt/lists/* 26 | 27 | # Copy the built binary and necessary files from the builder stage 28 | COPY --from=builder /app/target/release/jitstreamer-eb /usr/local/bin/jitstreamer-eb 29 | 30 | # Set the default working directory 31 | WORKDIR /app 32 | RUN mkdir -p /var/lib/lockdown 33 | RUN mkdir -p /etc/wireguard 34 | 35 | # Expose Wireguard and Jitstreamer ports 36 | EXPOSE 51869/udp 37 | EXPOSE 9172/tcp 38 | 39 | VOLUME /var/lib/lockdown 40 | VOLUME /etc/wireguard 41 | VOLUME /app/jitstreamer.db 42 | 43 | # Command to start all required services and run the program 44 | CMD ["/bin/bash", "-c", "wg-quick up jitstreamer & jitstreamer-eb"] 45 | -------------------------------------------------------------------------------- /install-docs/host-on-oracle-cloud-instructions.md: -------------------------------------------------------------------------------- 1 | # How to Self-Host JITStreamer-EB Using Oracle Cloud VM 2 | 3 | ## What... is this? 4 | 5 | For a long time I have been pondering how to enable JIT without PC access. After numerous frustrations, I have concluded that there is no way to start from scratch without any PC usage. However, if you want to self-host JITStreamer-EB without PC after the initial setup, this is a not-so-detailed guide to offer you some help. 6 | 7 | Another way to self-host without PC is using UTM SE as a server, and set up VPN tunnel between UTM SE and your iDevice. Unfortunately, UTM SE is painfully slow and may take you several hours (or days) to set up the environment. Additionally, UTM SE must be in the foreground to keep itself running, taking up a huge proportion of your monitor space. Therefore, UTM SE is somehow impratical for daily use, and thus this guide was born. 8 | 9 | Some paragraphs were copied from the referenced guides. Big thanks to everyone contributed to this project. 10 | 11 | ### TL;DR 12 | 13 | Pros 14 | 15 | - You don't need a PC after generating a certain file. 16 | 17 | - You don't need to run UTM SE. Faster and (far) less resource-intensive. 18 | 19 | - Self-hosting. No queueing or server down time (unless Oracle bans you for random reasons). 20 | 21 | Cons 22 | 23 | - Oracle obtains your personal information during registration. And the cloud VM is under their control. 24 | 25 | ## Credit and Sources 26 | 27 | The GOAT jkcoxson created this beautiful project for us. Here's his [GitHub](https://github.com/jkcoxson) and here's the specific [project](https://github.com/jkcoxson/JitStreamer-EB) of interest. Also its [website](https://jkcoxson.com/jitstreamer). He provided this to everyone for free. You should consider donating to him to show appreciation and support this type of work in the future. 28 | 29 | Please consider checking the following guides when you run into errors! 30 | This guide is heavily derived from them. Big thanks. 31 | 32 | - [JITStreamer-EB Docker Installation Guide by Unlearned6688](https://github.com/jkcoxson/JitStreamer-EB/blob/master/install-docs/jitstreamer-eb-debian-docker-instructions.md) 33 | 34 | - [JITStreamer-EB Tailscale Setup Guide by EnderRobber101](https://github.com/jkcoxson/JitStreamer-EB/blob/master/install-docs/jitstreamer-eb-debian-docker-tailscale-instructions.md) 35 | 36 | [Oracle Cloud](https://www.oracle.com/cloud/) but of course you can choose other cloud VM providers. I chose Oracle because they provide free VMs with sufficient power to accomplish the task. 37 | 38 | ## The guide starts here! 39 | 40 | ### Oracle Cloud: From Signing Up to Creating Instance 41 | 42 | 1. Sign up [Oracle Cloud Free Tier](https://www.oracle.com/cloud/free/). 43 | 44 | - You have to fill in your credit card information in order to use the free service, but you won't be actually charged if you do not upgrade your tier (I think so). There is a test charge after you fill in your card info, it will not actually cost anything. 45 | 46 | - Of course you can choose other cloud computing provider as you wish. 47 | 48 | 2. After registration, create a VM instance on your dashboard with following arguments: 49 | 50 | - `Image`: I chose `Ubuntu 22.04 minimal`. But I think any distro should work as expected. 51 | 52 | - `Shape`: `VM.Standard.E2.1.Micro`. To ensure always-free usage. 53 | 54 | - `SSH keys`: `Generate a key pair for me` and `Save private key` because you will need to send a certain file via [SSH](https://wiki.archlinux.org/title/Secure_Shell). 55 | 56 | 3. Press the `Create` button. 57 | 58 | 4. You will be redirected to the `Instance details` page. After a short while your VM will turn green which means it's ready. 59 | 60 | 5. Connect the VM by [SSH](https://wiki.archlinux.org/title/Secure_Shell) its public IP or via the web UI console (On the page showing `Instance details`, click `Resources` -> `Console connection` and then click `Launch Cloud Shell Connection`). 61 | 62 | How to connect via SSH is not the main topic of this guide. You can take a look at the following references, depending on your operating system: 63 | 64 | - Windows: [PuTTY](https://www.putty.org/); [Simple Guide](https://docs.cfengine.com/docs/3.25/getting-started-installation-pre-installation-checklist-putty-quick-start-guide.html) 65 | 66 | - Mac (I do not own Mac so I cannot make sure whether it works or not): [Use built-in terminal](https://www.servermania.com/kb/articles/ssh-mac) 67 | 68 | - [Linux](https://wiki.archlinux.org/title/Secure_Shell) 69 | 70 | - iOS/iPadOS: I use [ShellFish](https://apps.apple.com/us/app/ssh-files-secure-shellfish/id1336634154). 71 | 72 | Use the private key generated during instance creation to authenticate your SSH session. 73 | 74 | If are using a command line interface, you could SSH using the following command. 75 | Replace `path_to_your_private_key_file`, `vm_username` and `cloud_vm_public_ip` with the real ones you get. 76 | 77 | ``` 78 | ssh -i path_to_your_private_key_file vm_username@cloud_vm_public_ip 79 | ``` 80 | 81 | For example: 82 | 83 | ``` 84 | ssh -i ~/ssh-key-2025-02-29.key ubuntu@69.42.0.114 85 | ``` 86 | 87 | ### Prerequisite 1: iDevice Paring File (This is the only step that requires a PC!) 88 | 89 | #### [Jitterbugpair](https://github.com/osy/Jitterbug) 90 | 91 | Follow the instructions from this page: [Sidestore](https://docs.sidestore.io/docs/getting-started/pairing-file) 92 | 93 | Btw, arch users may use the [jitterbugpair-bin AUR](https://docs.sidestore.io/docs/getting-started/pairing-file). 94 | 95 | After this step you will obtain a file named `YOURDEVICEUDID.mobiledevicepairing` (for example `00008111-111122223333801E.mobiledevicepairing`). 96 | 97 | #### Rename the generated mobiledevicepairing file 98 | 99 | Rename the extension (`mobiledevicepairing`) to `.plist`. 100 | 101 | Now the filename should look like this: `00008111-111122223333801E.plist` 102 | 103 | #### Copy to Cloud VM via SSH 104 | 105 | Suppose you followed the guide and chose ubuntu for the operation system of your VM. 106 | Then you could type the following command into your terminal and send the pairing file to the home folder of your VM. 107 | 108 | ``` 109 | scp -i path_to_your_private_key_file path_to_the_pairing_file ubuntu@cloud_vm_public_ip:/home/ubuntu/ 110 | ``` 111 | 112 | For example: 113 | 114 | ``` 115 | scp -i ~/ssh-key-2025-02-29.key ~/00008111-111122223333801E.plist ubuntu@69.42.0.114:/home/ubuntu/ 116 | ``` 117 | 118 | ### Prerequisite 2: Docker 119 | 120 | On your cloud VM (via SSH or cloud console), 121 | follow [the official documentation](https://docs.docker.com/engine/install/debian/#install-using-the-repository) to setup Docker. 122 | Specifically, follow step 1. to 3. on the linked section. 123 | 124 | ### Prereuisite 3: Tailscale 125 | 126 | We need Tailscale to conveniently connect iDevice to the cloud VM. 127 | 128 | Follow instructions on [Tailscale website](https://tailscale.com/kb/1020/install-ios) to setup tailscale on your iDevice. 129 | 130 | For the cloud VM, follow the [official guide](https://tailscale.com/kb/1031/install-linux). 131 | 132 | ### Prerequisite 4: JitStreamer EB Docker Image 133 | 134 | This section is under construction. 135 | 136 | 1. clone the repository 137 | 138 | ``` 139 | git clone https://github.com/jkcoxson/JitStreamer-EB 140 | ``` 141 | 142 | 2. change your working directory to the cloned repo. 143 | 144 | ``` 145 | cd JitStreamer-EB 146 | ``` 147 | 148 | 3. provide the iDevice pairing file to JitStreamer-EB, remember to replace the example filename. 149 | 150 | ``` 151 | mkdir lockdown 152 | mv ~/00008111-111122223333801E.plist lockdown 153 | ``` 154 | 155 | 4. create database file 156 | 157 | if the cloud vm does not have `sqlite3` installed, install it first: `sudo apt install sqlite3` 158 | 159 | ``` 160 | mkdir app 161 | sqlite3 ./jitstreamer.db < ./src/sql/up.sql 162 | sqlite3 163 | ``` 164 | 165 | When the terminal shows `sqlite>`, 166 | type in the following command and execute them separately 167 | (do not type multiple lines and execute at once) 168 | 169 | ``` 170 | .open jitstreamer.db 171 | ``` 172 | ``` 173 | INSERT INTO DEVICES (udid, ip, last_used) VALUES ([udid], [ip], CURRENT_TIMESTAMP); 174 | ``` 175 | 176 | (Follwing notes are taken from [this guide](https://github.com/jkcoxson/JitStreamer-EB/blob/master/install-docs/jitstreamer-eb-debian-docker-instructions.md), 177 | for troubleshooting and verifying the results, 178 | please consult the guide.) 179 | 180 | Replace the [udid] and [ip] (so, the second set. The two with the brackets!) with (examples)'00008111-111122223333801E' and '192.168.1.2' 181 | 182 | Note 1: The above UDID is FAKE. INSERT YOUR OWN UDID! I used a fake one which resembles a real one to help visually. Please... don't copy that into your database. 183 | 184 | Note 2: The brackets are now deleted. They are replaced with ' (NOT ") 185 | 186 | Note 3: The IP in question is the TAILSCALE IP of the iDevice. The IP of your iPhone, etc. Each device needs to have a different IP. 187 | 188 | Note 4: You HAVE to add "::ffff:" in front of the regular IPv4 IP address. eg: ::ffff:192.168.1.2 189 | 190 | This is a FAKE but realistic example. Yours will contain your own UDID and IP: 191 | 192 | `INSERT INTO DEVICES (udid, ip, last_used) VALUES ('00008111-111122223333801E', '::ffff:192.168.1.2', CURRENT_TIMESTAMP);` 193 | 194 | `Ctrl D` to exit sqlite. 195 | 196 | 5. Build and run 197 | 198 | `docker build -t jitstreamer-eb .` 199 | 200 | `docker compose up -d` 201 | 202 | ### Prerequisite 5: The magical shortcut on your iDevice 203 | 204 | 1. download shortcut 205 | 206 | On your iDevice (iPhone or iPad), visit the [JITStreamer-EB official site](https://jkcoxson.com/jitstreamer). 207 | 208 | You don't need to `Download Wireguard`. 209 | `Download Shortcut` alone is enough. 210 | 211 | 2. edit the shortcut 212 | 213 | In the Shortcuts app, long press to edit the shortcut. 214 | 215 | Delete all the steps that contains `Wireguard`. 216 | 217 | (The following section is taken from [this guide](https://github.com/jkcoxson/JitStreamer-EB/blob/master/install-docs/jitstreamer-eb-debian-docker-tailscale-instructions.md).) 218 | 219 | Under the long introduction from jkcoxson, you'll see a section with a yellow-colored icon called "Text". In the editable area it will have `http://[an-ip]:9172` 220 | 221 | Change it so that it matches the TAILSCALE IP of your HOST machine. The machine you are running Docker on. Example: `http://100.168.10.37:9172` Obviously the IP would be your own IP. 222 | 223 | Hit "Done" in the upper-right corner. 224 | 225 | ### Execution!!!!! 226 | 227 | Execute the shortcut and then profit. 228 | 229 | ## You are strong. 230 | 231 | Feel free to edit this guide and make it more detailed and comprehensive! -------------------------------------------------------------------------------- /install-docs/jitstreamer-eb-debian-docker-instructions.md: -------------------------------------------------------------------------------- 1 | # How to Self-Host JITStreamer-EB (For Dummies) 2 | 3 | ## What is this? 4 | 5 | I write these little guides for myself and sometimes like to share. My goal is only to provide more precise and full instructions to those who have very little to no knowledge of the required tools. 6 | 7 | This guide will be going over how to get JITStreamer-EB (I'll refer to it as the "project" in the future to shorten my typing) up and running in Docker. Specifically using Docker Compose. Docker Compose offers, in my opinion, a much easier deployment of Docker containers. This is absolutely not the only way to run this project. 8 | 9 | ## Credit and Sources 10 | 11 | The GOAT jkcoxson created this beautiful project for us. Here's his [GitHub](https://github.com/jkcoxson) and here's the specific [project](https://github.com/jkcoxson/JitStreamer-EB) of interest. Also its [website](https://jkcoxson.com/jitstreamer). He provided this to everyone for free. You should consider donating to him to show appreciation and support this type of work in the future. 12 | 13 | [Docker](https://www.docker.com/) of course 14 | 15 | [SideStore](https://docs.sidestore.io) I used one of their documents to cut down on me re-typing. Another excellent project worth checking out separately. 16 | 17 | [jkcoxson's Discord](https://discord.gg/cRDk9PN9zu) Small but helpful and nice community. They put together and troubleshot issues with self-hosting and getting everything to work well with Docker Compose within days of jkcoxson going live with the project. It was beautiful to witness and take a small part in. 18 | 19 | Here's some other sites and specific links I found personally useful for random "How do I do..." questions: 20 | 21 | [Sqlite Tutorial](https://www.sqlitetutorial.net/) 22 | 23 | [Creating and Deleting Databases and Tables](https://www.prisma.io/dataguide/sqlite/creating-and-deleting-databases-and-tables) 24 | 25 | [Inserting and Deleting Data](https://www.prismagraphql.com/dataguide/sqlite/inserting-and-deleting-data) 26 | 27 | [UFW in Debian](https://wiki.debian.org/Uncomplicated%20Firewall%20%28ufw%29) 28 | 29 | ## Prerequisites 30 | 31 | Before beginning, you'll need to "gather your tools" and "parts" required to build the end project. 32 | 33 | I will be writing this guide with an eye towards installing things on Linux. Specifically, I'm currently running a derivative of Debian called "MX Linux". I tell you this because no guide can be universal. However, Docker offers something closer to universality across operating systems. Just be aware that the way directories work and such are different. Permissions are handled differently. Not everything will always work the same way. 34 | 35 | ### Install [Docker](https://docs.docker.com/engine/install/debian/) with the Docker Compose plugin. 36 | 37 | Go to the hyperlink (Debian users) or otherwise locate your required version of Docker. Here's the instructions for Debian (if you're using another OS, just use your relevant instructions then move on). 38 | 39 | **This is me assuming you are installing Docker on a fresh OS** (as I am too!). Go to the link and follow instructions if you need to "clean out" old stuff. (You probably do not need to). 40 | 41 | 1. Add the Docker sources 42 | 43 | ``` 44 | # Add Docker's official GPG key: 45 | sudo apt-get update 46 | sudo apt-get install ca-certificates curl 47 | sudo install -m 0755 -d /etc/apt/keyrings 48 | sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc 49 | sudo chmod a+r /etc/apt/keyrings/docker.asc 50 | 51 | # Add the repository to Apt sources: 52 | echo \ 53 | "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \ 54 | $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ 55 | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null 56 | sudo apt-get update 57 | ``` 58 | 59 | 2. Install 60 | 61 | ```sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin``` 62 | 63 | 3. Test 64 | 65 | ```sudo docker run hello-world``` 66 | 67 | The above step should pop out a message beginning with "Hello from Docker!" and a long message after that. If so, good, Docker is installed. We're going to add our user to the docker group so that we don't need to type "sudo docker" constantly. You can skip this if you don't care about typing sudo and your password constantly. 68 | 69 | **Note: I feel compelled to mention that adding your user to the "docker" group is considered a possible security risk. I won't comment further, but I will leave the [link](https://docs.docker.com/engine/security/#docker-daemon-attack-surface) from the Docker website directly. This was brought to my attention by user "@Laes" in the Discord (Thank you for informing me.) I won't delete the information below, but I will say if you go to the link and read it and don't fully comprehend what it says, then maybe just stick to typing ```sudo docker```. As Laes put it, (paraphrasing) "You only have to type ```sudo docker``` a few times then never again." Which is true, and I agree with this sentiment now.** 70 | 71 | Copy/pasted from [here](https://docs.docker.com/engine/install/linux-postinstall/). Go there for more details. It's pretty straight forward, though. 72 | 73 | ```sudo groupadd docker``` 74 | 75 | ```sudo usermod -aG docker $USER``` 76 | 77 | ```newgrp docker``` 78 | 79 | ```docker run hello-world``` 80 | 81 | You should once again get the "Hello from Docker" message. Yay. Docker is ready to dock... stuff? 82 | 83 | ## Part I - Preparation 84 | 85 | With Docker set up, you need to create some more files, place them in specific locations, and download some stuff. On we go. 86 | 87 | ### Clone the Repo 88 | 89 | **Note: I (unlearned6688) have begun pushing Docker image builds to my hub.docker.com account sardine0006. If you'd like to skip building the image yourself, you can pull the image manually ```docker image pull sardine0006/jitstreamer-eb:latest``` or just edit your docker-compose.yml and make the "image:" portion say ```sardine0006/jitstreamer-eb:latest``` If you do grab tbe pre-built image, you can also skip the build image portion down in the "Exeuction" section. Otherwise, if you prefer to build yourself, continue on!** 90 | 91 | Again, this is all from a Debian perspective. Adjust commands as required. Google will help you. Many of these are now the same using tools like Powershell on Windows. MacOS is Unix based and thus already basically the same for most stuff. 92 | 93 | I will be cloning the repo into my "home" directory. It will look like ```/home/USER/JitStreamer-EB``` where USER is... you know. Your username that you login with. You can also shortcut to it with ```~/JitStreamer-EB```. Just know the directory where you will be building. 94 | 95 | Clone the repo to your local server/PC. Change directory to the newly cloned repo. 96 | 97 | ``` 98 | git clone https://github.com/jkcoxson/JitStreamer-EB 99 | cd JitStreamer-EB 100 | ``` 101 | 102 | ### Create a Pairing File 103 | 104 | You can create a pairing file many ways. You can create them in any OS and copy them to this docker container. So, if you find it easier to make them in Windows, then do so. Whatever makes you happy. 105 | 106 | I will be copying the [instructions](https://docs.sidestore.io/docs/getting-started/pairing-file/) from the SideStore project on how to get a pairing file with Linux. They have instructions for Windows and MacOS as well. Follow the link for downloads and more instructions. 107 | 108 | 1. Extract the Jitterbug zip file, and open a terminal (if you haven't already) to the extracted directory. 109 | 2. In that terminal, run ```chmod +x ./jitterbugpair``` 110 | 3. Plug your device into your computer, and open your device to its home screen. Once done, execute the program in your terminal with ```./jitterbugpair``` 111 | 4. If you get a prompt saying you need to trust the computer from your iDevice, make sure to do so. You may need to rerun jitterbugpair. 112 | 5. Once it is done, you will get a file that ends with .mobiledevicepairing in the directory you ran jitterbugpair from. 113 | 6. Transfer this file to your device in a way of your choosing. Zipping the file before sending it off is the best way to ensure the pairing file won't break during transport 114 | 7. Transferring using cloud storage may change the file's extension (most likely turning into a .txt file), so be careful. It is also possible to change the extension to .plist for use with older SideStore versions, like 0.1.1. 115 | 116 | Note: You **DO** need to change the extension to .plist. Change it now! 117 | 118 | Note 2: Make a pairing file for each iDevice you want to user JitStreamer-EB for. iPhone and iPad means you need two different pairing files. 119 | 120 | Note 3: (Holy notes) You will need your UDID for each device later as well. These pairing files have their UDID in the names! Wow! So make sure to note which UDID belongs to each iDevice you own. This will save you brain-pain later. 121 | 122 | ### Copy Pairing Files (.plist) to JitStreamer-EB 123 | 124 | You can do this via GUI (with a file manager) or in the terminal. Whatever brings you happiness and joy. GUI is probably easiest for most people. In which case just copy the file(s), go to ```~/JitStreamer-EB``` and paste it in the "lockdown" directory. Otherwise, here's terminal instructions: 125 | 126 | 1. In terminal, go to whatever directory you ran jitterbug in when you created your pairing file. The default is the Downloads directory 127 | 128 | ```cd ~/Downloads``` 129 | 130 | 2. Make the lockdown directory for the plist files. (If you ran the container already for some reason, this directory already exists.) 131 | 132 | ```mkdir ~/JitStreamer-EB/lockdown``` 133 | 134 | 3. Type "ls" first to list all the files. It makes copy/paste easier with these huge file names. 135 | 136 | ```ls``` 137 | 138 | 4. Note: The below file name is FAKE. You need to use your own file with your own device's UDID. The ls command will show all your file names. Just copy the relevant ones. 139 | 140 | ```cp 00008111-111122223333801E.plist ~/JitStreamer-EB/lockdown``` 141 | 142 | Easy! 143 | 144 | ### Create Your Database File 145 | 146 | This part might be a bit weird. Prepare yourself. 147 | 148 | 1. You have to create the jitstreamer.db file (sqlite database) using build instructions included in the repo. 149 | 150 | ``` 151 | mkdir app 152 | sqlite3 ./jitstreamer.db < ./src/sql/up.sql 153 | ``` 154 | 155 | 2. Now you have a fancy little database. But you need to add your device info into it. 156 | 157 | Note: There are likely many ways to achieve the desired result here. This is just how I did it. It may be more steps than required, but it lets you see how the database works internally which I prefer for myself. 158 | 159 | Type into the terminal 160 | 161 | ```sqlite3``` 162 | 163 | Something like this will appear (maybe different versions): 164 | 165 | ``` 166 | SQLite version 3.40.1 2022-12-28 14:03:47 167 | Enter ".help" for usage hints. 168 | Connected to a transient in-memory database. 169 | Use ".open FILENAME" to reopen on a persistent database. 170 | ``` 171 | 172 | Note that it says "transient in-memory database". We don't want that! 173 | It's an easy fix. 174 | 175 | Type into the terminal after the "sqlite>" (which you should see): 176 | 177 | ```.open jitstreamer.db``` 178 | 179 | Optional: "Is everything ok so far?" check. Type: 180 | 181 | ```.tables``` 182 | 183 | You should see: 184 | 185 | ```devices downloads launch_queue``` 186 | 187 | This means your tables inside the database were created as instructed above. Good, good. 188 | 189 | 3. **Read the entire next part, including the notes and everything. Don't just blindly copy/paste! You must edit stuff!** 190 | 191 | Type this command from the repository to add your device information to the DEVICES table. 192 | 193 | ```INSERT INTO DEVICES (udid, ip, last_used) VALUES ([udid], [ip], CURRENT_TIMESTAMP);``` 194 | 195 | Replace the [udid] and [ip] (so, the second set. The two with the brackets!) with (examples)'00008111-111122223333801E' and '192.168.1.2' 196 | 197 | Note 1: The above UDID is FAKE. INSERT YOUR OWN UDID! I used a fake one which resembles a real one to help visually. Please... don't copy that into your database. 198 | 199 | Note 2: The brackets are now deleted. They are replaced with ' (NOT ") 200 | 201 | Note 3: The IP in question is the IP of the iDevice. The IP of your iPhone, etc. Each device needs to have a different IP. 202 | 203 | Note 4: You **HAVE** to add "::ffff:" in front of the regular IPv4 IP address. eg: ```::ffff:192.168.1.2``` 204 | 205 | This is a FAKE but realistic example. Yours will contain your own UDID and IP. 206 | 207 | ```INSERT INTO DEVICES (udid, ip, last_used) VALUES ('00008111-111122223333801E', '::ffff:192.168.1.2', CURRENT_TIMESTAMP);``` 208 | 209 | Now you can quickly check "Did I do this correctly?" (The casing is important here) 210 | 211 | **Note: That semicolon on the end (;) is REQUIRED for this command to work correctly! We love our ; Don't drop it** 212 | 213 | ```SELECT * FROM devices;``` 214 | 215 | You should see something like this for each device you inserted above. 216 | 217 | ```::ffff:192.168.1.2|00008111-111122223333801E|2025-01-31 15:17:50``` 218 | 219 | If you got the above response, then you are done with database creation. 220 | 221 | 4. Press "Ctrl Key + D" (two keys) to exit from the sqlite screen. 222 | 223 | ## Part II - The Execution 224 | 225 | We got everything created and placed where it needs to be. We're ready to do some JITing. 226 | 227 | ### Build and Run the Docker Image 228 | 229 | **NOTE: Step 1 below (building the image) is NO LONGER NECESSARY! If you wish to build the image, then by all means, feel free. However, if you're docker-compose.yml file has ```image: jkcoxson/jitstreamer-eb:latest``` then you can skip to Step 2 below. Your Docker image will be automatically pulled and run once it's downloaded!** 230 | 231 | 1. Build the docker image! This takes a bit to download then compile. Ensure it finishes with no errors. 232 | 233 | Note: you are still in ```~/JitStreamer-EB``` when you run this! 234 | 235 | ```docker build -t jitstreamer-eb .``` 236 | 237 | 2. Create a docker container using the Docker image you just made. Note: you are still in ```~/JitStreamer-EB``` when you run this! 238 | 239 | ```docker compose up -d``` 240 | 241 | 3. I like to see the logs running live with: 242 | 243 | ```docker logs -t -f jitstreamer-eb``` 244 | 245 | You should see a long stream of stuff happening. Good, good. (Ctrl + C will take you out of this screen) 246 | 247 | ### Setting Up the Shortcut on Your iDevice 248 | 249 | 1. On your iPhone or iPad, go to this [site](https://jkcoxson.com/jitstreamer) 250 | 251 | 2. Go to the bottom. Download that Shortcut. You will also need the Shortcuts app for iOS, obviously. Download it if you need to from the Apple App Store. 252 | 253 | 3. Open Shortcuts on iOS 254 | 255 | 4. Locate the JitStreamer EB shortcut in the list 256 | 257 | 5. Long press on it 258 | 259 | 6. Select the option "Edit" 260 | 261 | 7. Locate the IP section that needs changed. Under the long introduction from jkcoxson, you'll see a section with a yellow-colored icon called "Text". In the editable area it will have http://[an-ip]:9172 . This is the area we will be changing. 262 | 263 | 8. Change it so that it matches the IP of your HOST machine. The machine you are running Docker on. Example: 264 | 265 | ```http://192.168.1.3:9172``` 266 | 267 | Obviously the IP would be your own IP. 268 | 269 | 9. Hit "Done" in the upper-right corner. 270 | 271 | ### Oh Yeah, It's All Coming Together... Time to JIT 272 | 273 | Pre-flight checklist 274 | 275 | 1. Your docker container is running on your host machine without any crazy errors. Type ```docker ps``` to see a list of running containers. 276 | 277 | 2. Your host machine (where the Docker container is running) is on the same network as your iDevice. You can change this later, but for testing purposes at first, be on the same network to eliminate that as a possible source of problems. 278 | 279 | 3. Your edited the Shortcut from jkcoxson's website as instructed. 280 | 281 | Good? 282 | 283 | Tap the Shortcut to run it! 284 | 285 | If you have the Docker container logs running still (I recommend you do!) you will see some stuff happening as your iDevice connects to your host and docker container. Do not leave the Shortcuts app. Just wait a moment. It takes a couple seconds. If everything is going ok, it will pop up with a list of apps. You will select the app you want to enable JIT for. The shortcut and container will begin working again. Give it another moment to finish. Eventually it will say something like "You are 0 in the queue." After which (give it another moment!) your app should launch automatically. 286 | 287 | If that happens, congratulations. You are JITing. 288 | 289 | ## Troubleshooting and (Short) FAQ 290 | 291 | This section will be for the future mostly. It's hard for me to predict or know what issues people will encounter. I tried to explain to the best of my ability, but it's possible I forgot things or overlooked things. 292 | 293 | ### Firewall and Network Issues 294 | 295 | On the general topic of "Network issues" it's hard to provide much advice because every issue is specific. If you are having issue with connecting and acquiring JIT with your iDevice, and you already did some googling around online and reading, then my best recommendation is join the discord and search there. Ask if search doesn't provide much help. 296 | 297 | On firewall stuff I can offer more information in specific regard to Debian. Debian ships with (to my knowledge, this is standard install) a firewall called "Uncomplicated FireWall" or "UFW". You can access it with the command ```sudo ufw --help``` 298 | 299 | I bring up ufw because I was unable to connect my iDevice to my Debian host (and then to the Docker container) until I allowed my iDevice IP through UFW. Example: 300 | 301 | ```sudo ufw allow from 192.168.1.2``` 302 | 303 | ```sudo ufw allow to 192.168.1.2``` 304 | 305 | Then it worked perfectly. It may also work if you allow the port 9172, but I haven't tried. The above worked. Good enough for me. 306 | 307 | **Update on the above point: ```sudo ufw allow 9172``` DOES WORk** 308 | 309 | ### Network_bridge mode with Docker compose? 310 | 311 | **YES** It does work. Or, it should, anyway. 312 | Here's my working docker-compose.yml to use network bridge mode. At some point this will probably get added to the repo separately. 313 | 314 | ``` 315 | services: 316 | jitstreamer-eb: 317 | container_name: jitstreamer-eb 318 | #network_mode: host 319 | volumes: 320 | - ./lockdown:/var/lib/lockdown 321 | # - ./wireguard:/etc/wireguard 322 | - ./jitstreamer.db:/app/jitstreamer.db 323 | environment: 324 | - RUST_LOG=info 325 | - RUNNER_COUNT=1 326 | - ALLOW_REGISTRATION=2 327 | ports: 328 | - 9172:9172 329 | cap_add: 330 | - NET_ADMIN 331 | devices: 332 | - /dev/net/tun:/dev/net/tun 333 | image: jkcoxson/jitstreamer-eb:latest 334 | restart: unless-stopped 335 | ``` 336 | Note that this docker-compose file will: run in network bridge mode, not create a wireguard directory or .conf file, and exposes on port 9172. 337 | 338 | ### What does this work with? 339 | 340 | I saw a lot of discussion day one about what this (and all known JIT methods, to my knowledge) works with. 341 | 342 | TL;DR: **Only sideloaded apps using a developer certificate.** 343 | 344 | (Free or paid. Free is still arbitrarily limited by Apple to only 3 active apps, 10 apps per week total. That is an Apple limitation. Please compalain to them if you also find it annoying.) 345 | 346 | "But I purchased a cert from xxxx and I can't see my apps! Why?!" 347 | 348 | It isn't a developer cert. It's just a distribution cert. You have to contact the seller of that cert if you want to change it to a developer cert. This has nothing to do with this project. It's an Apple limitation, ultimately. 349 | 350 | You can also purchase your own Apple Developer Account ($99/year) and sideload using that. 351 | 352 | Or use the free developer account that every Apple Account has. You could sideload using something like SideStore, AltStore or (I haven't personally tried this one for JIT- mileage may vary) Sideloadly. This uses your personal, free developer certificate and thus allows you to access JIT on the sideloaded apps. 353 | 354 | My personal setup is: 355 | 356 | - a cheap distribution cert used for non-JIT apps. 357 | 358 | - SideStore for apps I want JIT for. 359 | 360 | But do whatever makes you happy. Just remember it's on Apple for any and all of these limitations. Or why we must use special tools to acquire JIT in the first place. 361 | -------------------------------------------------------------------------------- /install-docs/jitstreamer-eb-debian-docker-tailscale-instructions.md: -------------------------------------------------------------------------------- 1 | # How to Self-Host JITStreamer-EB with Tailscale 2 | Thank you to [jkcoxson](https://github.com/jkcoxson) for creating this project, [Unlearned6688](https://github.com/Unlearned6688) for creating [jitstreamer-eb-debian-docker-instructions.md](https://github.com/jkcoxson/JitStreamer-EB/blob/master/install-docs/jitstreamer-eb-debian-docker-instructions.md) which I will be referencing multiple times through this instructions, and others who had contributed to this project. 3 | 4 | You will first need to follow [these instructions](https://github.com/jkcoxson/JitStreamer-EB/blob/master/install-docs/jitstreamer-eb-debian-docker-instructions.md#prerequisites) until you reach [this section](https://github.com/jkcoxson/JitStreamer-EB/blob/master/install-docs/jitstreamer-eb-debian-docker-instructions.md#create-your-database-file) (meaning don't do the steps in the second link). 5 | 6 | ### Installing Tailscale 7 | First install tailscale **AND ACTIVATE IT** on your iDevice. Watch [this](https://www.youtube.com/watch?v=sPdvyR7bLqI) if you need help. 8 | 9 | On your host device, run the following to install tailscale and authenticate to connect it to your account. 10 | Linux 11 | ``` 12 | curl -fsSL https://tailscale.com/install.sh | sh 13 | tailscale up 14 | ``` 15 | If you are using any other devices go to [this website](https://tailscale.com/download) and continue the instructions from there 16 | 17 | Write down the host device's Tailscale IP and iDevice's Tailscale IP. DO NOT MIX THEM UP 18 | 19 | ### Create Your Database File 20 | For this step, I will be heavily referencing and slightly modifying [this section](https://github.com/jkcoxson/JitStreamer-EB/blob/master/install-docs/jitstreamer-eb-debian-docker-instructions.md#create-your-database-file). 21 | 22 | This part might be a bit weird. Prepare yourself. 23 | 24 | 1. You have to create the jitstreamer.db file (sqlite database) using build instructions included in the repo. 25 | 26 | ``` 27 | mkdir app 28 | sqlite3 ./jitstreamer.db < ./src/sql/up.sql 29 | ``` 30 | 31 | 2. Now you have a fancy little database. But you need to add your device info into it. 32 | 33 | Note: There are likely many ways to achieve the desired result here. This is just how I did it. It may be more steps than required, but it lets you see how the database works internally which I prefer for myself. 34 | 35 | Type into the terminal 36 | 37 | ``sqlite3`` 38 | 39 | Something like this will appear (maybe different versions): 40 | 41 | ``` 42 | SQLite version 3.40.1 2022-12-28 14:03:47 43 | Enter ".help" for usage hints. 44 | Connected to a transient in-memory database. 45 | Use ".open FILENAME" to reopen on a persistent database. 46 | ``` 47 | 48 | Note that it says "transient in-memory database". We don't want that! 49 | It's an easy fix. 50 | 51 | Type into the terminal after the "sqlite>" (which you should see): 52 | 53 | ``.open jitstreamer.db`` 54 | 55 | Optional: "Is everything ok so far?" check. Type: 56 | 57 | ``.tables`` 58 | 59 | You should see: 60 | 61 | ``devices downloads launch_queue mount_queue`` 62 | 63 | This means your tables inside the database were created as instructed above. Good, good. 64 | 65 | 3. **Read the entire next part, including the notes and everything. Don't just blindly copy/paste! You must edit stuff!** 66 | 67 | Type this command from the repository to add your device information to the DEVICES table. 68 | 69 | ``INSERT INTO DEVICES (udid, ip, last_used) VALUES ([udid], [ip], CURRENT_TIMESTAMP);`` 70 | 71 | Replace the [udid] and [ip] (so, the second set. The two with the brackets!) with (examples)'00008111-111122223333801E' and '100.100.35.52' 72 | 73 | Note 1: The above UDID is FAKE. INSERT YOUR OWN UDID! I used a fake one which resembles a real one to help visually. Please... don't copy that into your database. 74 | 75 | Note 2: The brackets are now deleted. They are replaced with ' (NOT ") 76 | 77 | Note 3: The IP in question is the **TAILSCALE** IP of the iDevice. The **TAILSCALE** IP of your iPhone, etc. Each device needs to have a different IP. 78 | 79 | Note 4: You **HAVE** to add "::ffff:" in front of the **TAILSCALE** IP address. eg: ``::ffff:100.100.35.52`` 80 | 81 | This is a FAKE but realistic example. Yours will contain your own UDID and IP. 82 | 83 | ``INSERT INTO DEVICES (udid, ip, last_used) VALUES ('00008111-111122223333801E', '::ffff:100.100.35.52', CURRENT_TIMESTAMP);`` 84 | 85 | Now you can quickly check "Did I do this correctly?" (The casing is important here) 86 | 87 | **Note: That semicolon on the end (;) is REQUIRED for this command to work correctly! We love our ; Don't drop it** 88 | 89 | ``SELECT * FROM devices;`` 90 | 91 | You should see something like this for each device you inserted above. 92 | 93 | ``::ffff:100.100.35.52|00008111-111122223333801E|2025-01-31 15:17:50`` 94 | 95 | If you got the above response, then you are done with database creation. 96 | 97 | 4. Press "Ctrl Key + D" (two keys) to exit from the sqlite screen. 98 | 99 | 100 | ### Next Steps 101 | Continue following the steps linked [here](https://github.com/jkcoxson/JitStreamer-EB/blob/master/install-docs/jitstreamer-eb-debian-docker-instructions.md#part-ii---the-execution) until [here](https://github.com/jkcoxson/JitStreamer-EB/blob/master/install-docs/jitstreamer-eb-debian-docker-instructions.md#setting-up-the-shortcut-on-your-idevice). 102 | 103 | ### Setting Up Your iDevice 104 | 105 | * Go to this [site](https://jkcoxson.com/jitstreamer) 106 | * Go to the bottom. Download that Shortcut. You will also need the Shortcuts app for iOS, obviously. 107 | * Open Shortcuts on iOS 108 | * Locate the JitStreamer EB shortcut in the list 109 | * Long press on it 110 | * Select the option "Edit" 111 | * Locate the IP section that needs changed. Under the long introduction from jkcoxson, you'll see a section with a yellow-colored icon called "Text". In the editable area it will have http://[an-ip]:9172 112 | * Change it so that it matches the TAILSCALE IP of your HOST machine. The machine you are running Docker on. Example: 113 | ``http://100.168.10.37:9172`` 114 | Obviously the IP would be your own IP. 115 | * Hit "Done" in the upper-right corner. 116 | 117 | Continue on from [here](https://github.com/jkcoxson/JitStreamer-EB/blob/master/install-docs/jitstreamer-eb-debian-docker-instructions.md#oh-yeah-its-all-coming-together-time-to-jit) 118 | 119 | This is not the best guide ever written but feel free to contribute and edit this. 120 | -------------------------------------------------------------------------------- /install-docs/jitstreamer-eb-ubuntu-raspberry-pi-instructions.md: -------------------------------------------------------------------------------- 1 | # Installing JITStreamer on raspberry pi (works offline, sort of) 2 | 3 | ## Supported devices 4 | 5 | Tested: 6 | * Raspberry PI 4 B with iPad type C 7 | 8 | Other devices might work but I don't know yet. (Contribute if you figure something out) 9 | 10 | ## Step 1 - Enable OTG 11 | 12 | By following tutorials such as [this](https://blog.hardill.me.uk/2019/11/02/pi4-usb-c-gadget/), you should be able to enable OTG on your device. OTG means the type C port on the pi (4) will act as a power and data transfer (an ethernet port). This tutorial is slightly outdated and not exactly what we need so I would recommend using [this repo's](https://github.com/techcraftco/rpi-usb-gadget) prebuilt image. Specifically, [this](https://github.com/techcraftco/rpi-usb-gadget/releases/download/v0.4/ubuntu-server-arm64-22.04-arm64.img.zip) ubuntu server image. 13 | 14 | 15 | 16 | ## Step 2 - Basic Setup 17 | SSH into the pi 18 | # Update 19 | Run the update commands 20 | ``` 21 | sudo apt update 22 | sudo apt upgrade 23 | ``` 24 | When prompted to change dns settings, type 'N' and press enter. I will keep the existing files. 25 | 26 | # Docker 27 | Install by following [this tutorial](https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository) from the official docker website. 28 | 29 | ## JITStreamer 30 | 31 | Follow the normal step from [here](https://github.com/jkcoxson/JitStreamer-EB/blob/master/install-docs/jitstreamer-eb-debian-docker-instructions.md#part-i---preparation), and stop when you reach [here](https://github.com/jkcoxson/JitStreamer-EB/blob/master/install-docs/jitstreamer-eb-debian-docker-instructions.md#create-your-database-file) 32 | 33 | Here is a modified version of the step for raspberry pi 34 | 35 | ### Create Your Database File 36 | 37 | This part might be a bit weird. Prepare yourself. 38 | 39 | 1. You have to create the jitstreamer.db file (sqlite database) using build instructions included in the repo. 40 | 41 | ``` 42 | mkdir app 43 | sqlite3 ./jitstreamer.db < ./src/sql/up.sql 44 | ``` 45 | 46 | 2. Now you have a fancy little database. But you need to add your device info into it. 47 | 48 | Note: There are likely many ways to achieve the desired result here. This is just how I did it. It may be more steps than required, but it lets you see how the database works internally which I prefer for myself. 49 | 50 | Type into the terminal 51 | 52 | ```sqlite3``` 53 | 54 | If sqlite3 is not installed, run 55 | ```sudo apt install sqlite3``` 56 | 57 | Something like this will appear (maybe different versions): 58 | 59 | ``` 60 | SQLite version 3.40.1 2022-12-28 14:03:47 61 | Enter ".help" for usage hints. 62 | Connected to a transient in-memory database. 63 | Use ".open FILENAME" to reopen on a persistent database. 64 | ``` 65 | 66 | Note that it says "transient in-memory database". We don't want that! 67 | It's an easy fix. 68 | 69 | Type into the terminal after the "sqlite>" (which you should see): 70 | 71 | ```.open jitstreamer.db``` 72 | 73 | Optional: "Is everything ok so far?" check. Type: 74 | 75 | ```.tables``` 76 | 77 | You should see: 78 | 79 | ```devices downloads launch_queue mount_queue``` 80 | 81 | This means your tables inside the database were created as instructed above. Good, good. 82 | **Edit the following commands with YOUR udid** 83 | Type each command one by one 84 | ```INSERT INTO DEVICES (udid, ip, last_used) VALUES ('YOUR UDID', '::ffff:10.55.0.2', CURRENT_TIMESTAMP);``` 85 | ```INSERT INTO DEVICES (udid, ip, last_used) VALUES ('YOUR UDID', '::ffff:10.55.0.3', CURRENT_TIMESTAMP);``` 86 | ```INSERT INTO DEVICES (udid, ip, last_used) VALUES ('YOUR UDID', '::ffff:10.55.0.4', CURRENT_TIMESTAMP);``` 87 | ```INSERT INTO DEVICES (udid, ip, last_used) VALUES ('YOUR UDID', '::ffff:10.55.0.5', CURRENT_TIMESTAMP);``` 88 | ```INSERT INTO DEVICES (udid, ip, last_used) VALUES ('YOUR UDID', '::ffff:10.55.0.6', CURRENT_TIMESTAMP);``` 89 | 90 | 91 | Ex. 92 | ```INSERT INTO DEVICES (udid, ip, last_used) VALUES ('00008111-111122223333801E', '::ffff:10.55.0.2', CURRENT_TIMESTAMP);``` 93 | 94 | Now you can quickly check "Did I do this correctly?" (The casing is important here) 95 | 96 | **Note: That semicolon on the end (;) is REQUIRED for this command to work correctly! We love our ; Don't drop it** 97 | 98 | ```SELECT * FROM devices;``` 99 | 100 | You should see something like this 101 | 102 | ``` 103 | ::ffff:10.55.0.2|00008111-111122223333801E|2025-01-31 15:17:50 104 | ::ffff:10.55.0.3|00008111-111122223333801E|2025-01-31 15:17:50 105 | ::ffff:10.55.0.4|00008111-111122223333801E|2025-01-31 15:17:50 106 | ::ffff:10.55.0.5|00008111-111122223333801E|2025-01-31 15:17:50 107 | ::ffff:10.55.0.6|00008111-111122223333801E|2025-01-31 15:17:50 108 | ``` 109 | 110 | If you got the above response, then you are done with database creation. 111 | 112 | 4. Press "Ctrl Key + D" (two keys) to exit from the sqlite screen. 113 | 114 | ## Shortcut 115 | 116 | ### Next 117 | Follow the rest of the tutorial from [here](https://github.com/jkcoxson/JitStreamer-EB/blob/master/install-docs/jitstreamer-eb-debian-docker-instructions.md#part-ii---the-execution). We will set up the shortcut after this step. 118 | 119 | ### Setting Up the Shortcut on Your iDevice 120 | 121 | 1. On your iPhone or iPad, go to this [site](https://jkcoxson.com/jitstreamer) 122 | 123 | 2. Go to the bottom. Download that Shortcut. You will also need the Shortcuts app for iOS, obviously. Download it if you need to from the Apple App Store. 124 | 125 | 3. Open Shortcuts on iOS 126 | 127 | 4. Locate the JitStreamer EB shortcut in the list 128 | 129 | 5. Long press on it 130 | 131 | 6. Select the option "Edit" 132 | 133 | 7. Locate the IP section that needs changed. Under the long introduction from jkcoxson, you'll see a section with a yellow-colored icon called "Text". In the editable area it will have http://[an-ip]:9172 . This is the area we will be changing. 134 | 135 | 8. Change it so it is ```10.55.0.1```. Example: 136 | 137 | ```http://10.55.0.1:9172``` 138 | 139 | 9. Hit "Done" in the upper-right corner. 140 | 141 | # Running 142 | Use a type c to c cable and connect the raspberry pi power port to the type c ipad. Go to setting and wait until a ethernet device pops up. Click the ethernet device and when you see an ip address that is ```10.55.0.x``` instead of something random, you are ready to run the shortcut. 143 | 144 | # F&Q 145 | ### How does this work without wifi? 146 | The type c cable acts like a ethernet cable between the devices, creating a local network. 147 | 148 | 149 | As always, contribute if you find something more. 150 | -------------------------------------------------------------------------------- /install-docs/macos.md: -------------------------------------------------------------------------------- 1 | # Usage on MacOS 2 | 3 | ## Building 4 | Clone and build the following repositories: 5 | 6 | [netmuxd](https://github.com/jkcoxson/netmuxd) 7 | [tunneld-rs](https://github.com/jkcoxson/tunneld-rs) 8 | [JitStreamer-EB](https://github.com/jkcoxson/JitStreamer-EB) 9 | 10 | ```bash 11 | # Clone repositories 12 | git clone https://github.com/jkcoxson/netmuxd.git 13 | git clone https://github.com/jkcoxson/tunneld-rs.git 14 | git clone https://github.com/jkcoxson/JitStreamer-EB.git 15 | 16 | # Build each project 17 | cd netmuxd && cargo build --release 18 | cd ../tunneld-rs && cargo build --release 19 | cd ../JitStreamer-EB && cargo build --release 20 | ``` 21 | 22 | ## Usage 23 | If you have a weird Python and brew dumpster fire like I do, create a venv 24 | and install the dependencies: 25 | 26 | ```bash 27 | mkdir venv 28 | python3 -m venv venv 29 | pip3 install requests aiosqlite pymobiledevice3 30 | ``` 31 | 32 | Download the [runners](../src/runners) folder to your folder 33 | 34 | ## Running Services 35 | Run the following commands in 3 separate terminals: 36 | 37 | 1. Tunneld: 38 | ```bash 39 | sudo RUST_LOG=info USBMUXD_SOCKET_ADDRESS=127.0.0.1:27015 ./target/release/tunneld-rs 40 | ``` 41 | 42 | 2. Netmuxd: 43 | ```bash 44 | sudo RUST_LOG=info ./target/release/netmuxd --disable-unix --host 127.0.0.1 --plist-storage ~/Desktop/plist_storage 45 | ``` 46 | 47 | 3. JitStreamer: 48 | ```bash 49 | RUST_LOG=info PLIST_STORAGE=~/Desktop/plist_storage ALLOW_REGISTRATION=2 USBMUXD_SOCKET_ADDRESS=127.0.0.1:27015 ./target/release/jitstreamer-eb 50 | ``` 51 | 52 | ## Final Steps 53 | 1. Get your shortcut from [jkcoxson.com/jitstreamer](https://jkcoxson.com/jitstreamer) 54 | 2. Change the IP to your Mac's IP address 55 | 3. Go to `your.macs.ip:9172/upload` 56 | 4. Submit your pairing file 57 | 5. Profit 58 | -------------------------------------------------------------------------------- /install-docs/unraid-os-install-instructions.md: -------------------------------------------------------------------------------- 1 | # How to Self-Host JITStreamer-EB (For Dummies) (Now in unRAID Flavor!) 2 | 3 | ## What is this? 4 | 5 | I write these little guides for myself and sometimes like to share. My goal is only to provide more precise and full instructions to those who have very little to no knowledge of the required tools. 6 | 7 | This guide will be going over how to get JITStreamer-EB (I'll refer to it as the "project" in the future to shorten my typing) up and running in Docker. Specifically using Docker Compose. Docker Compose offers, in my opinion, a much easier deployment of Docker containers. This is absolutely not the only way to run this project. 8 | 9 | **Note: This is a condensed version for unRAID OS. I did tbis quickly and made it by cutting out a ton of stuff not neede to run 10 | it on unRAID OS.** 11 | 12 | ## Credit and Sources 13 | 14 | The GOAT jkcoxson created this beautiful project for us. Here's his [GitHub](https://github.com/jkcoxson) and here's the specific [project](https://github.com/jkcoxson/JitStreamer-EB) of interest. Also its [website](https://jkcoxson.com/jitstreamer). He provided this to everyone for free. You should consider donating to him to show appreciation and support this type of work in the future. 15 | 16 | [Docker](https://www.docker.com/) of course 17 | 18 | [SideStore](https://docs.sidestore.io) I used one of their documents to cut down on me re-typing. Another excellent project worth checking out separately. 19 | 20 | [jkcoxson's Discord](https://discord.gg/cRDk9PN9zu) Small but helpful and nice community. They put together and troubleshot issues with self-hosting and getting everything to work well with Docker Compose within days of jkcoxson going live with the project. It was beautiful to witness and take a small part in. 21 | 22 | Here's some other sites and specific links I found personally useful for random "How do I do..." questions: 23 | 24 | [Sqlite Tutorial](https://www.sqlitetutorial.net/) 25 | 26 | [Creating and Deleting Databases and Tables](https://www.prisma.io/dataguide/sqlite/creating-and-deleting-databases-and-tables) 27 | 28 | [Inserting and Deleting Data](https://www.prismagraphql.com/dataguide/sqlite/inserting-and-deleting-data) 29 | 30 | [UFW in Debian](https://wiki.debian.org/Uncomplicated%20Firewall%20%28ufw%29) 31 | 32 | ## Part I - Preparation 33 | 34 | ### Create a Pairing File 35 | 36 | You can create a pairing file many ways. You can create them in any OS and copy them to this docker container. So, if you find it easier to make them in Windows, then do so. Whatever makes you happy. 37 | 38 | I will be copying the [instructions](https://docs.sidestore.io/docs/getting-started/pairing-file/) from the SideStore project on how to get a pairing file with Linux. They have instructions for Windows and MacOS as well. Follow the link for downloads and more instructions. 39 | 40 | 1. Extract the Jitterbug zip file, and open a terminal (if you haven't already) to the extracted directory. 41 | 42 | 2. In that terminal, run ```chmod +x ./jitterbugpair``` 43 | 44 | 3. Plug your device into your computer, and open your device to its home screen. Once done, execute the program in your terminal with ```./jitterbugpair``` 45 | 46 | 4. If you get a prompt saying you need to trust the computer from your iDevice, make sure to do so. You may need to rerun jitterbugpair. 47 | 48 | 5. Once it is done, you will get a file that ends with .mobiledevicepairing in the directory you ran jitterbugpair from. 49 | 50 | 6. Transfer this file to your device in a way of your choosing. Zipping the file before sending it off is the best way to ensure the pairing file won't break during transport 51 | 52 | 7. Transferring using cloud storage may change the file's extension (most likely turning into a .txt file), so be careful. It is also possible to change the extension to .plist for use with older SideStore versions, like 0.1.1. 53 | 54 | Note: You **DO** need to change the extension to .plist. Change it now! 55 | 56 | Note 2: Make a pairing file for each iDevice you want to user JitStreamer-EB for. iPhone and iPad means you need two different pairing files. 57 | 58 | Note 3: (Holy notes) You will need your UDID for each device later as well. These pairing files have their UDID in the names! Wow! So make sure to note which UDID belongs to each iDevice you own. This will save you brain-pain later. 59 | 60 | ### Copy Pairing Files (.plist) to JitStreamer-EB 61 | 62 | **NOTE: I don't recommend doing any of this below in unRAID. unRAID isn't really meant to be used for these types of operations. You probably can (I haven't tested), but just to be clear: I don't think you should. You can make the .plist files in a VM, on a regular Windows boot, etc. and transfer them to your unRAID install. 63 | 64 | You can do this via GUI (with a file manager) or in the terminal. Whatever brings you happiness and joy. GUI is probably easiest for most people. In which case just copy the file(s), go to ```~/JitStreamer-EB``` and paste it in the "lockdown" directory. Otherwise, here's terminal instructions: 65 | 66 | 1. In terminal, go to whatever directory you ran jitterbug in when you created your pairing file. The default is the Downloads directory 67 | ```cd ~/Downloads``` 68 | 69 | 2. Make the lockdown directory for the plist files. (If you ran the container already for some reason, this directory already exists.) 70 | 71 | ```mkdir ~/JitStreamer-EB/lockdown``` 72 | 73 | 3. Type "ls" first to list all the files. It makes copy/paste easier with these huge file names. 74 | 75 | ```ls``` 76 | 77 | 4. Note: The below file name is FAKE. You need to use your own file with your own device's UDID. The ls command will show all your file names. Just copy the relevant ones. 78 | 79 | ```cp 00008111-111122223333801E.plist ~/JitStreamer-EB/lockdown``` 80 | Easy! 81 | 82 | ### Create Your Database File 83 | 84 | This part might be a bit weird. Prepare yourself. 85 | 86 | 1. You have to create the jitstreamer.db file (sqlite database) using build instructions included in the repo. I modified this a bit from the Debian instructions I had written previously since you aren't cloning the repo on unRAID. There's likely a faster/better way to do this. This will work though. 87 | 88 | Make the database directory 89 | 90 | ```mkdir /mnt/user/appdata/jitstreamer-eb/app``` 91 | 92 | Make the database file 93 | 94 | ```touch /mnt/user/appdata/jitstreamer-eb/app/jitstreamer.db``` 95 | 96 | 2. Now you have a fancy little database. But you need to add your device info into it. 97 | Note: There are likely many ways to achieve the desired result here. This is just how I did it. It may be more steps than required, but it lets you see how the database works internally which I prefer for myself. 98 | Type into the terminal 99 | ```sqlite3 /mnt/user/appdata/jitstreamer-eb/app/jitstreamer.db``` 100 | Something like this will appear (maybe different versions): 101 | 102 | ``` 103 | SQLite version 3.40.1 2022-12-28 14:03:47 104 | Enter ".help" for usage hints. 105 | Connected to a transient in-memory database. 106 | Use ".open FILENAME" to reopen on a persistent database. 107 | 108 | ``` 109 | 110 | Note that it says "transient in-memory database". We don't want that! 111 | It's an easy fix. 112 | 113 | Type into the terminal after the "sqlite>" (which you should see): 114 | 115 | ```.open jitstreamer.db``` 116 | 117 | **NOTE: This part is different for unRAID. Make sure to copy the below code directly into the new database file!** 118 | 119 | [This code](https://github.com/jkcoxson/JitStreamer-EB/blob/master/src/sql/up.sql) needs to be pasted into the now-open for editing database. Check the link for the most up-to-date code to ccpy/paste. 120 | 121 | ``` 122 | create table devices ( 123 | ip varchar(15) primary key, 124 | udid varchar(40) not null, 125 | last_used datetime not null 126 | ); 127 | 128 | 129 | create table downloads ( 130 | code varchar(40) primary key, 131 | contents varchar(255) not null 132 | ); 133 | 134 | create table launch_queue ( 135 | udid varchar(40) not null, 136 | ip varchar(32) not null, 137 | bundle_id varchar(255) not null, 138 | status int not null, -- 0: pending, 2: error 139 | error varchar(255), 140 | ordinal integer primary key 141 | ); 142 | ``` 143 | 144 | Optional: "Is everything ok so far?" check. Type: 145 | 146 | ```.tables``` 147 | 148 | You should see: 149 | 150 | ```devices downloads launch_queue``` 151 | 152 | This means your tables inside the database were created as instructed above. Good, good. 153 | 154 | 3. **Read the entire next part, including the notes and everything. Don't just blindly copy/paste! You must edit stuff!** 155 | 156 | Type this command from the repository to add your device information to the DEVICES table. 157 | 158 | ```INSERT INTO DEVICES (udid, ip, last_used) VALUES ([udid], [ip], CURRENT_TIMESTAMP);``` 159 | 160 | Replace the [udid] and [ip] (so, the second set. The two with the brackets!) with (examples)'00008111-111122223333801E' and '192.168.1.2' 161 | 162 | Note 1: The above UDID is FAKE. INSERT YOUR OWN UDID! I used a fake one which resembles a real one to help visually. Please... don't copy that into your database. 163 | 164 | Note 2: The brackets are now deleted. They are replaced with ' (NOT ") 165 | 166 | Note 3: The IP in question is the IP of the iDevice. The IP of your iPhone, etc. Each device needs to have a different IP. 167 | 168 | Note 4: You **HAVE** to add "::ffff:" in front of the regular IPv4 IP address. eg: 169 | 170 | ```::ffff:192.168.1.2``` 171 | 172 | This is a FAKE but realistic example. Yours will contain your own UDID and IP. 173 | 174 | ```INSERT INTO DEVICES (udid, ip, last_used) VALUES ('00008111-111122223333801E', '::ffff:192.168.1.2', CURRENT_TIMESTAMP);``` 175 | 176 | Now you can quickly check "Did I do this correctly?" (The casing is important here) 177 | 178 | **Note: That semicolon on the end (;) is REQUIRED for this command to work correctly! We love our ; Don't drop it** 179 | 180 | ```SELECT * FROM devices;``` 181 | 182 | You should see something like this for each device you inserted above. 183 | 184 | ```::ffff:192.168.1.2|00008111-111122223333801E|2025-01-31 15:17:50``` 185 | 186 | If you got the above response, then you are done with database creation. 187 | 188 | 4. Press "Ctrl Key + D" (two keys) to exit from the sqlite screen. 189 | 190 | ## Part II - The Execution 191 | 192 | We got everything created and placed where it needs to be. We're ready to do some JITing. 193 | 194 | ### Setting Up the Shortcut on Your iDevice 195 | 196 | 1. On your iPhone or iPad, go to this [site](https://jkcoxson.com/jitstreamer) 197 | 198 | 2. Go to the bottom. Download that Shortcut. You will also need the Shortcuts app for iOS, obviously. Download it if you need to from the Apple App Store. 199 | 200 | 3. Open Shortcuts on iOS 201 | 202 | 4. Locate the JitStreamer EB shortcut in the list 203 | 204 | 5. Long press on it 205 | 206 | 6. Select the option "Edit" 207 | 208 | 7. Locate the IP section that needs changed. Under the long introduction from jkcoxson, you'll see a section with a yellow-colored icon called "Text". In the editable area it will have http://[an-ip]:9172 . This is the area we will be changing. 209 | 210 | 8. Change it so that it matches the IP of your HOST machine. The machine you are running Docker on. Example: 211 | ```http://192.168.1.3:9172``` 212 | Obviously the IP would be your own IP. 213 | 214 | 9. Hit "Done" in the upper-right corner. 215 | 216 | ### Oh Yeah, It's All Coming Together... Time to JIT 217 | 218 | Pre-flight checklist 219 | 220 | 1. Your docker container is running on your host machine without any crazy errors. Type ```docker ps``` to see a list of running containers. 221 | 222 | 2. Your host machine (where the Docker container is running) is on the same network as your iDevice. You can change this later, but for testing purposes at first, be on the same network to eliminate that as a possible source of problems. 223 | 224 | 3. Your edited the Shortcut from jkcoxson's website as instructed. 225 | 226 | Good? 227 | 228 | Tap the Shortcut to run it! 229 | 230 | If you have the Docker container logs running still (I recommend you do!) you will see some stuff happening as your iDevice connects to your host and docker container. Do not leave the Shortcuts app. Just wait a moment. It takes a couple seconds. If everything is going ok, it will pop up with a list of apps. You will select the app you want to enable JIT for. The shortcut and container will begin working again. Give it another moment to finish. Eventually it will say something like "You are 0 in the queue." After which (give it another moment!) your app should launch automatically. 231 | 232 | If that happens, congratulations. You are JITing. 233 | -------------------------------------------------------------------------------- /jitstreamer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # ASCII Art Header 4 | cat << "EOF" 5 | ___ __ _____ __ __________ 6 | / (_) /_/ ___// /_________ ____ _____ ___ ___ _____ / ____/ __ ) 7 | __ / / / __/\__ \/ __/ ___/ _ \/ __ `/ __ `__ \/ _ \/ ___/_____/ __/ / __ | 8 | / /_/ / / /_ ___/ / /_/ / / __/ /_/ / / / / / / __/ / /_____/ /___/ /_/ / 9 | \____/_/\__//____/\__/_/ \___/\__,_/_/ /_/ /_/\___/_/ /_____/_____/ 10 | 11 | CREATED BY michaell._. 12 | 13 | EOF 14 | 15 | sleep 2 16 | # Check if running on Debian or Ubuntu 17 | if ! grep -qi 'ubuntu\|debian' /etc/os-release; then 18 | echo "This script only works on debian-based systems. Exiting..." 19 | exit 1 20 | fi 21 | 22 | # Uninstall conflicting packages 23 | echo "Uninstalling any conflicting Docker packages..." 24 | for pkg in docker.io docker-doc docker-compose podman-docker containerd runc; do 25 | sudo apt-get remove -y $pkg 26 | done 27 | 28 | # Function to set up Docker's apt repository for Debian 29 | setup_debian_docker_repo() { 30 | sudo apt-get update 31 | sudo apt-get install -y ca-certificates curl 32 | sudo install -m 0755 -d /etc/apt/keyrings 33 | sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc 34 | sudo chmod a+r /etc/apt/keyrings/docker.asc 35 | 36 | echo \ 37 | "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \ 38 | $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ 39 | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null 40 | } 41 | 42 | # Function to set up Docker's apt repository for Ubuntu 43 | setup_ubuntu_docker_repo() { 44 | echo "Setting up Docker's apt repository for Ubuntu..." 45 | sudo apt-get update 46 | sudo apt-get install -y ca-certificates curl 47 | sudo install -m 0755 -d /etc/apt/keyrings 48 | sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc 49 | sudo chmod a+r /etc/apt/keyrings/docker.asc 50 | echo \ 51 | "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ 52 | $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \ 53 | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null 54 | } 55 | 56 | # Check if Debian or Ubuntu and set repository accordingly 57 | if grep -qi 'ubuntu' /etc/os-release; then 58 | setup_ubuntu_docker_repo 59 | elif grep -qi 'debian' /etc/os-release; then 60 | setup_debian_docker_repo 61 | fi 62 | 63 | # Update package index again 64 | sudo apt-get update 65 | 66 | # Install Docker packages 67 | echo "Installing Docker..." 68 | sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin 69 | 70 | # Enable Docker and containerd to start on boot 71 | echo "Enabling Docker and containerd to start on boot..." 72 | sudo systemctl enable docker.service 73 | sudo systemctl enable containerd.service 74 | 75 | 76 | 77 | # Set permissions for 'jitstreamer-eb' folder 78 | # Set permissions for 'jitstreamer-eb' folder 79 | JIT_DIR="${HOME}/JitStreamer-EB" 80 | if [ ! -d "$JIT_DIR" ]; then 81 | echo "JitStreamer-EB directory not found. Creating it and setting permissions..." 82 | mkdir -p "$JIT_DIR" 83 | sudo chown $(whoami):$(whoami) "$JIT_DIR" 84 | sudo chmod 755 "$JIT_DIR" 85 | else 86 | echo "JitStreamer-EB directory exists. Setting permissions..." 87 | sudo chown $(whoami):$(whoami) "$JIT_DIR" 88 | sudo chmod 755 "$JIT_DIR" 89 | fi 90 | 91 | # Debugging: Print the directory 92 | echo "JitStreamer-EB directory path: $JIT_DIR" 93 | 94 | 95 | # Check if JitStreamer-EB directory exists, if not, clone it 96 | if [ ! -d "$JIT_DIR" ]; then 97 | echo "JitStreamer-EB directory not found in home directory. Cloning from GitHub..." 98 | git clone https://github.com/jkcoxson/JitStreamer-EB.git "$JIT_DIR" || { echo "Failed to clone repository. Exiting..."; exit 1; } 99 | else 100 | echo "JitStreamer-EB directory already exists. Checking for updates..." 101 | cd "$JIT_DIR" || { echo "Failed to change directory to $JIT_DIR. Exiting..."; exit 1; } 102 | 103 | # Check if the directory is a Git repository 104 | if [ ! -d ".git" ]; then 105 | echo "The JitStreamer-EB directory is not a valid Git repository." 106 | echo "Would you like to remove the existing directory and clone it again? (y/n)" 107 | read -r response 108 | if [[ "$response" =~ ^[Yy]$ ]]; then 109 | rm -rf "$JIT_DIR" || { echo "Failed to remove existing directory. Exiting..."; exit 1; } 110 | git clone https://github.com/jkcoxson/JitStreamer-EB.git "$JIT_DIR" || { echo "Failed to clone repository. Exiting..."; exit 1; } 111 | else 112 | echo "Exiting without making changes." 113 | exit 1 114 | fi 115 | else 116 | echo "Updating the existing repository..." 117 | git pull || { echo "Failed to update repository. Exiting..."; exit 1; } 118 | fi 119 | fi 120 | 121 | cd "$JIT_DIR" || { echo "Failed to change directory to $JIT_DIR. Exiting..."; exit 1; } 122 | 123 | # Install dependencies 124 | echo "Installing dependencies..." 125 | sudo apt update && sudo apt install -y usbmuxd sqlite3 unzip libimobiledevice6 libimobiledevice-utils 126 | 127 | # Create necessary directories 128 | echo "Creating necessary directories..." 129 | mkdir -p lockdown 130 | mkdir -p wireguard 131 | 132 | # Check if database exists and has tables 133 | if [ ! -f ./jitstreamer.db ]; then 134 | echo "Creating new database..." 135 | sqlite3 ./jitstreamer.db < ./src/sql/up.sql 136 | else 137 | echo "Database already exists, checking tables..." 138 | # Check if devices table exists 139 | TABLE_EXISTS=$(sqlite3 ./jitstreamer.db "SELECT name FROM sqlite_master WHERE type='table' AND name='devices';") 140 | if [ -z "$TABLE_EXISTS" ]; then 141 | echo "Tables not found, initializing database..." 142 | sqlite3 ./jitstreamer.db < ./src/sql/up.sql 143 | fi 144 | fi 145 | 146 | # Ask user for the pairing file 147 | echo "Please enter the full path to your pairing file or place it in this directory:" 148 | echo "$PWD" 149 | read -r pairing_path 150 | if [ -f "$pairing_path" ]; then 151 | # Extract filename from path 152 | filename=$(basename "$pairing_path") 153 | # Create plist filename 154 | plist_filename="${filename%.*}.plist" 155 | # Copy and rename 156 | cp "$pairing_path" "./lockdown/$plist_filename" 157 | echo "Copied pairing file to lockdown directory as $plist_filename" 158 | else 159 | echo "Pairing file not found at the specified location. Exiting..." 160 | exit 1 161 | fi 162 | 163 | # Get UDID from the plist file name 164 | UDID=$(basename -s .plist lockdown/*.plist) 165 | 166 | # Check if UDID already exists in database 167 | EXISTING_UDID=$(sqlite3 jitstreamer.db "SELECT udid FROM devices WHERE udid='$UDID';") 168 | 169 | if [ ! -z "$EXISTING_UDID" ]; then 170 | echo "Device with UDID $UDID already exists in database. Skipping insertion." 171 | else 172 | # Ask for device IP 173 | echo "Please enter your device's IP address (e.g., 192.168.1.2):" 174 | read -r device_ip 175 | 176 | # Add ::ffff: prefix to the IP address 177 | ipv6_ip="::ffff:$device_ip" 178 | 179 | # Create SQL command 180 | SQL_COMMAND="INSERT INTO devices (udid, ip, last_used) VALUES ('$UDID', '$ipv6_ip', CURRENT_TIMESTAMP);" 181 | 182 | # Execute SQL command 183 | echo "Adding device to database..." 184 | sqlite3 jitstreamer.db "$SQL_COMMAND" 185 | 186 | # Verify the entry was added 187 | echo "Verifying database entry..." 188 | sqlite3 jitstreamer.db "SELECT * FROM devices;" 189 | fi 190 | 191 | # Build the Docker image 192 | echo "Building the Docker image..." 193 | sudo docker build -t jitstreamer-eb . 194 | 195 | # Run the Docker container using Docker Compose 196 | echo "Running the Docker container..." 197 | sudo docker compose up -d 198 | 199 | : ' 200 | # Create jitstreamer.sh in the home directory 201 | JITSTREAMER_SCRIPT="$HOME/jitstreamer.sh" 202 | cat << 'EOF' > "$JITSTREAMER_SCRIPT" 203 | #!/bin/bash 204 | 205 | # Name of the Docker container 206 | CONTAINER_NAME="jitstreamer-eb" 207 | JIT_DIR="${HOME}/JitStreamer-EB" # Path to the JitStreamer-EB directory 208 | 209 | # Function to start the container 210 | start_container() { 211 | echo "Starting the $CONTAINER_NAME container..." 212 | cd "$JIT_DIR" || { echo "Failed to change directory to $JIT_DIR. Exiting..."; exit 1; } 213 | sudo docker compose start "$CONTAINER_NAME" 214 | } 215 | 216 | # Function to stop the container 217 | stop_container() { 218 | echo "Stopping the $CONTAINER_NAME container..." 219 | cd "$JIT_DIR" || { echo "Failed to change directory to $JIT_DIR. Exiting..."; exit 1; } 220 | sudo docker compose stop "$CONTAINER_NAME" 221 | } 222 | 223 | # Function to restart the container 224 | restart_container() { 225 | echo "Restarting the $CONTAINER_NAME container..." 226 | cd "$JIT_DIR" || { echo "Failed to change directory to $JIT_DIR. Exiting..."; exit 1; } 227 | sudo docker compose restart "$CONTAINER_NAME" 228 | } 229 | 230 | # Check if at least one argument is provided 231 | if [ "$#" -eq 0 ]; then 232 | echo "No command provided. Use start, stop, restart, or logs." 233 | exit 1s 234 | fi 235 | 236 | # Handle the command 237 | case "$1" in 238 | start) 239 | start_container 240 | ;; 241 | stop) 242 | stop_container 243 | ;; 244 | restart) 245 | restart_container 246 | ;; 247 | 248 | *) 249 | echo "Invalid command. Use start, stop, restart." 250 | exit 1 251 | ;; 252 | esac 253 | EOF 254 | ' 255 | # Make jitstreamer.sh executable 256 | #chmod +x "$JITSTREAMER_SCRIPT" 257 | 258 | # Install the script itself to /usr/local/bin 259 | #SCRIPT_NAME="jitstreamereb" 260 | #INSTALL_PATH="/usr/local/bin/$SCRIPT_NAME" 261 | 262 | #if [ "$(id -u)" -ne 0 ]; then 263 | # echo "This script needs to be run with sudo to install itself." 264 | # exit 1 265 | #fi 266 | 267 | # Copy the script to /usr/local/bin 268 | #sudo cp "$JITSTREAMER_SCRIPT" "$INSTALL_PATH" 269 | #sudo chmod +x "$INSTALL_PATH" 270 | #echo "Script installed as $SCRIPT_NAME. You can now run it from anywhere." 271 | 272 | # Show usage examples and instructions 273 | #echo -e "\nUsage examples for the jitstreamereb command:" 274 | #echo "1. jitstreamereb start # To start the container" 275 | #echo "2. jitstreamereb stop # To stop the container" 276 | #echo "3. jitstreamereb restart # To restart the container" 277 | 278 | # Get the current IP address 279 | IP_ADDRESS=$(hostname -I | awk '{print $1}') 280 | echo -e "\nPlease change the address in your shortcut to the following format:" 281 | echo "http://$IP_ADDRESS:9172" 282 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | build: 2 | cargo build --release 3 | run: build 4 | sudo ./target/release/jitstreamer-eb 5 | docker-build: 6 | cargo clean 7 | sudo docker build -t jkcoxson/jitstreamer-eb . 8 | docker-run: 9 | sudo docker run --rm -it \ 10 | --name jitstreamer-eb \ 11 | -p 9172:9172 \ 12 | -p 51869:51869/udp \ 13 | -v jitstreamer-lockdown:/var/lib/lockdown \ 14 | -v jitstreamer-wireguard:/etc/wireguard \ 15 | -v $(pwd)/jitstreamer.db:/app/jitstreamer.db \ 16 | -e RUST_LOG=info \ 17 | --cap-add=NET_ADMIN \ 18 | --device /dev/net/tun:/dev/net/tun \ 19 | jitstreamer-eb 20 | docker-shell: 21 | sudo docker exec -it jitstreamer-eb /bin/bash 22 | -------------------------------------------------------------------------------- /src/common.rs: -------------------------------------------------------------------------------- 1 | // Jackson Coxson 2 | 3 | use idevice::pairing_file::PairingFile; 4 | use log::info; 5 | 6 | pub async fn get_udid_from_ip(ip: String) -> Result { 7 | tokio::task::spawn_blocking(move || { 8 | let db = match sqlite::open("jitstreamer.db") { 9 | Ok(db) => db, 10 | Err(e) => { 11 | info!("Failed to open database: {:?}", e); 12 | return Err(format!("Failed to open database: {:?}", e)); 13 | } 14 | }; 15 | 16 | // Get the device from the database 17 | let query = "SELECT udid FROM devices WHERE ip = ?"; 18 | let mut statement = match crate::db::db_prepare(&db, query) { 19 | Some(s) => s, 20 | None => { 21 | log::error!("Failed to prepare query!"); 22 | return Err("Failed to open database".to_string()); 23 | } 24 | }; 25 | statement.bind((1, ip.as_str())).unwrap(); 26 | let udid = if let Some(sqlite::State::Row) = crate::db::statement_next(&mut statement) { 27 | let udid = statement.read::("udid").unwrap(); 28 | info!("Found device with udid {}", udid); 29 | udid 30 | } else { 31 | info!("No device found for IP {:?}", ip); 32 | return Err(format!("No device found for IP {:?}", ip)); 33 | }; 34 | Ok(udid) 35 | }) 36 | .await 37 | .unwrap() 38 | } 39 | 40 | /// Gets the pairing file 41 | pub async fn get_pairing_file( 42 | udid: &str, 43 | pairing_file_storage: &str, 44 | ) -> Result { 45 | // All pairing files are stored at /var/lib/lockdown/.plist 46 | let path = format!("{pairing_file_storage}/{udid}.plist"); 47 | let pairing_file = tokio::fs::read(path).await?; 48 | 49 | PairingFile::from_bytes(&pairing_file) 50 | } 51 | -------------------------------------------------------------------------------- /src/db.rs: -------------------------------------------------------------------------------- 1 | // Jackson Coxson 2 | // Code to retry a few times until the database isn't locked. 3 | 4 | use sqlite::{Connection, State, Statement}; 5 | 6 | pub fn db_prepare<'a>(db: &'a Connection, query: &str) -> Option> { 7 | for _ in 0..50 { 8 | match db.prepare(query) { 9 | Ok(s) => return Some(s), 10 | Err(_) => { 11 | std::thread::sleep(std::time::Duration::from_millis(100)); 12 | } 13 | } 14 | } 15 | None 16 | } 17 | 18 | pub fn statement_next(statement: &mut Statement) -> Option { 19 | for _ in 0..50 { 20 | match statement.next() { 21 | Ok(s) => return Some(s), 22 | Err(_) => { 23 | std::thread::sleep(std::time::Duration::from_millis(100)); 24 | } 25 | } 26 | } 27 | None 28 | } 29 | -------------------------------------------------------------------------------- /src/heartbeat.rs: -------------------------------------------------------------------------------- 1 | // Jackson Coxson 2 | // Orchestrator for heartbeat threads 3 | 4 | use std::{collections::HashMap, net::IpAddr}; 5 | 6 | use idevice::{ 7 | heartbeat::HeartbeatClient, pairing_file::PairingFile, provider::TcpProvider, IdeviceError, 8 | IdeviceService, 9 | }; 10 | use log::debug; 11 | use tokio::sync::oneshot::error::TryRecvError; 12 | 13 | pub enum SendRequest { 14 | Store((String, tokio::sync::oneshot::Sender<()>)), 15 | Kill(String), 16 | } 17 | pub type NewHeartbeatSender = tokio::sync::mpsc::Sender; 18 | 19 | pub fn heartbeat() -> NewHeartbeatSender { 20 | let (sender, mut receiver) = tokio::sync::mpsc::channel::(100); 21 | tokio::task::spawn(async move { 22 | let mut cache: HashMap> = HashMap::new(); 23 | while let Some(msg) = receiver.recv().await { 24 | match msg { 25 | SendRequest::Store((udid, handle)) => { 26 | if let Some(old_sender) = cache.insert(udid, handle) { 27 | old_sender.send(()).ok(); 28 | } 29 | } 30 | SendRequest::Kill(udid) => { 31 | if let Some(old_sender) = cache.remove(&udid) { 32 | old_sender.send(()).ok(); 33 | } 34 | } 35 | } 36 | } 37 | }); 38 | sender 39 | } 40 | 41 | pub async fn heartbeat_thread( 42 | udid: String, 43 | ip: IpAddr, 44 | pairing_file: &PairingFile, 45 | ) -> Result, IdeviceError> { 46 | debug!("Connecting to device {udid} to get apps"); 47 | let provider = TcpProvider { 48 | addr: ip, 49 | pairing_file: pairing_file.clone(), 50 | label: "JitStreamer-EB".to_string(), 51 | }; 52 | 53 | let mut heartbeat_client = HeartbeatClient::connect(&provider).await?; 54 | 55 | let (sender, mut receiver) = tokio::sync::oneshot::channel::<()>(); 56 | 57 | tokio::task::spawn(async move { 58 | let interval = 30; 59 | loop { 60 | let _ = match heartbeat_client.get_marco(interval).await { 61 | Ok(interval) => interval, 62 | Err(e) => { 63 | debug!("Failed to get marco for {udid}: {e:?}"); 64 | break; 65 | } 66 | }; 67 | if heartbeat_client.send_polo().await.is_err() { 68 | debug!("Failed to send polo for {udid}"); 69 | break; 70 | } 71 | match receiver.try_recv() { 72 | Ok(_) => break, 73 | Err(TryRecvError::Closed) => break, 74 | Err(TryRecvError::Empty) => {} 75 | } 76 | } 77 | }); 78 | Ok(sender) 79 | } 80 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // Jackson Coxson 2 | // JitStreamer for the year of our Lord, 2025 3 | 4 | const VERSION: [u8; 3] = [0, 2, 0]; 5 | 6 | use std::{ 7 | collections::HashMap, 8 | net::{IpAddr, SocketAddr}, 9 | str::FromStr, 10 | }; 11 | 12 | use axum::{ 13 | extract::{Json, Path, State}, 14 | http::{header::CONTENT_TYPE, Method}, 15 | response::Html, 16 | routing::{any, get, post}, 17 | }; 18 | use axum_client_ip::SecureClientIp; 19 | use common::get_pairing_file; 20 | use heartbeat::NewHeartbeatSender; 21 | use idevice::{ 22 | core_device_proxy::CoreDeviceProxy, debug_proxy::DebugProxyClient, 23 | installation_proxy::InstallationProxyClient, provider::TcpProvider, IdeviceService, 24 | }; 25 | use log::{debug, info}; 26 | use serde::{Deserialize, Serialize}; 27 | use tower_http::cors::CorsLayer; 28 | 29 | mod common; 30 | mod db; 31 | mod heartbeat; 32 | mod mount; 33 | mod raw_packet; 34 | mod register; 35 | 36 | #[derive(Clone)] 37 | struct JitStreamerState { 38 | pub new_heartbeat_sender: NewHeartbeatSender, 39 | pub mount_cache: mount::MountCache, 40 | pub pairing_file_storage: String, 41 | } 42 | 43 | #[tokio::main] 44 | async fn main() { 45 | println!("Starting JitStreamer-EB, enabling logger"); 46 | dotenvy::dotenv().ok(); 47 | 48 | // Read the environment variable constants 49 | let allow_registration = std::env::var("ALLOW_REGISTRATION") 50 | .unwrap_or("1".to_string()) 51 | .parse::() 52 | .unwrap(); 53 | let port = std::env::var("JITSTREAMER_PORT") 54 | .unwrap_or("9172".to_string()) 55 | .parse::() 56 | .unwrap(); 57 | let pairing_file_storage = 58 | std::env::var("PLIST_STORAGE").unwrap_or("/var/lib/lockdown".to_string()); 59 | 60 | env_logger::init(); 61 | info!("Logger initialized"); 62 | 63 | // Run the environment checks 64 | if allow_registration == 1 { 65 | register::check_wireguard(); 66 | } 67 | if !std::fs::exists("jitstreamer.db").unwrap() { 68 | info!("Creating database"); 69 | let db = sqlite::open("jitstreamer.db").unwrap(); 70 | db.execute(include_str!("sql/up.sql")).unwrap(); 71 | } 72 | 73 | // Create a heartbeat manager 74 | let state = JitStreamerState { 75 | new_heartbeat_sender: heartbeat::heartbeat(), 76 | mount_cache: mount::MountCache::default(), 77 | pairing_file_storage, 78 | }; 79 | 80 | let cors = CorsLayer::new() 81 | .allow_methods([Method::GET, Method::POST, Method::OPTIONS]) 82 | .allow_origin(tower_http::cors::Any) 83 | .allow_headers([CONTENT_TYPE]); 84 | 85 | // Start with Axum 86 | let app = axum::Router::new() 87 | .layer(cors.clone()) 88 | .route("/hello", get(|| async { "Hello, world!" })) 89 | .route("/version", post(version)) 90 | .route("/mount", get(mount::check_mount)) 91 | .route("/mount_ws", any(mount::handler)) 92 | .route( 93 | "/mount_status", 94 | get(|| async { Html(include_str!("mount.html")) }), 95 | ) 96 | .route("/get_apps", get(get_apps)) 97 | .route("/launch_app/{bundle_id}", get(launch_app)) 98 | .route("/attach/{pid}", post(attach_app)) 99 | .route("/status", get(status)) // will be removed soon 100 | .with_state(state); 101 | 102 | let app = if allow_registration == 1 { 103 | app.route("/register", post(register::register)) 104 | } else if allow_registration == 2 { 105 | app.route("/register", post(register::register)) 106 | .route("/upload", get(register::upload)) 107 | } else { 108 | app 109 | }; 110 | 111 | let app = app 112 | .layer(axum_client_ip::SecureClientIpSource::ConnectInfo.into_extension()) 113 | .layer(cors); 114 | 115 | let addr = SocketAddr::new(IpAddr::from_str("::0").unwrap(), port); 116 | info!("Starting server on {:?}", addr); 117 | let listener = tokio::net::TcpListener::bind(&addr).await.unwrap(); 118 | axum::serve( 119 | listener, 120 | app.into_make_service_with_connect_info::(), 121 | ) 122 | .await 123 | .unwrap(); 124 | } 125 | 126 | #[derive(Serialize, Deserialize)] 127 | struct VersionRequest { 128 | version: String, 129 | } 130 | #[derive(Serialize, Deserialize)] 131 | struct VersionResponse { 132 | ok: bool, 133 | } 134 | 135 | async fn version(Json(version): Json) -> Json { 136 | info!("Checking version {}", version.version); 137 | 138 | // Parse the version as 3 numbers 139 | let version = version 140 | .version 141 | .split('.') 142 | .map(|v| v.parse::().unwrap_or(0)) 143 | .collect::>(); 144 | 145 | // Compare the version, compare each number 146 | for (i, v) in VERSION.iter().enumerate() { 147 | if version.get(i).unwrap_or(&0) < v { 148 | return Json(VersionResponse { ok: false }); 149 | } 150 | } 151 | 152 | Json(VersionResponse { ok: true }) 153 | } 154 | 155 | #[derive(Serialize, Deserialize, Clone)] 156 | struct GetAppsReturn { 157 | ok: bool, 158 | apps: Vec, 159 | bundle_ids: Option>, 160 | error: Option, 161 | } 162 | 163 | /// Gets the list of apps with get-task-allow on the device 164 | /// - Get the IP from the request and UDID from the database 165 | /// - Send the udid/IP to netmuxd for heartbeat-ing 166 | /// - Connect to the device and get the list of bundle IDs 167 | #[axum::debug_handler] 168 | async fn get_apps( 169 | ip: SecureClientIp, 170 | State(state): State, 171 | ) -> Json { 172 | let ip = ip.0; 173 | 174 | info!("Got request to get apps from {:?}", ip); 175 | 176 | let udid = match common::get_udid_from_ip(ip.to_string()).await { 177 | Ok(u) => u, 178 | Err(e) => { 179 | return Json(GetAppsReturn { 180 | ok: false, 181 | apps: Vec::new(), 182 | bundle_ids: None, 183 | error: Some(e), 184 | }) 185 | } 186 | }; 187 | 188 | // Get the pairing file 189 | debug!("Getting pairing file for {udid}"); 190 | let pairing_file = match get_pairing_file(&udid, &state.pairing_file_storage).await { 191 | Ok(pairing_file) => pairing_file, 192 | Err(e) => { 193 | info!("Failed to get pairing file: {:?}", e); 194 | return Json(GetAppsReturn { 195 | ok: false, 196 | apps: Vec::new(), 197 | bundle_ids: None, 198 | error: Some(format!("Failed to get pairing file: {:?}", e)), 199 | }); 200 | } 201 | }; 202 | 203 | // Heartbeat the device 204 | match heartbeat::heartbeat_thread(udid.clone(), ip, &pairing_file).await { 205 | Ok(s) => { 206 | state 207 | .new_heartbeat_sender 208 | .send(heartbeat::SendRequest::Store((udid.clone(), s))) 209 | .await 210 | .unwrap(); 211 | } 212 | Err(e) => { 213 | let e = match e { 214 | idevice::IdeviceError::InvalidHostID => { 215 | "your pairing file is invalid. Regenerate it with jitterbug pair.".to_string() 216 | } 217 | _ => e.to_string(), 218 | }; 219 | info!("Failed to heartbeat device: {:?}", e); 220 | return Json(GetAppsReturn { 221 | ok: false, 222 | apps: Vec::new(), 223 | bundle_ids: None, 224 | error: Some(format!("Failed to heartbeat device: {e}")), 225 | }); 226 | } 227 | } 228 | 229 | // Connect to the device and get the list of bundle IDs 230 | debug!("Connecting to device {udid} to get apps"); 231 | 232 | let provider = TcpProvider { 233 | addr: ip, 234 | pairing_file, 235 | label: "JitStreamer-EB".to_string(), 236 | }; 237 | 238 | let mut instproxy_client = match InstallationProxyClient::connect(&provider).await { 239 | Ok(i) => i, 240 | Err(e) => { 241 | return Json(GetAppsReturn { 242 | ok: false, 243 | apps: Vec::new(), 244 | bundle_ids: None, 245 | error: Some(format!("Failed to start instproxy: {e:?}")), 246 | }) 247 | } 248 | }; 249 | 250 | let apps = match instproxy_client 251 | .get_apps(Some("User".to_string()), None) 252 | .await 253 | { 254 | Ok(apps) => apps, 255 | Err(e) => { 256 | info!("Failed to get apps: {:?}", e); 257 | return Json(GetAppsReturn { 258 | ok: false, 259 | apps: Vec::new(), 260 | bundle_ids: None, 261 | error: Some(format!("Failed to get apps: {:?}", e)), 262 | }); 263 | } 264 | }; 265 | let mut apps: HashMap = apps 266 | .into_iter() 267 | .filter(|(_, app)| { 268 | // Filter out apps that don't have get-task-allow 269 | let app = match app { 270 | plist::Value::Dictionary(app) => app, 271 | _ => return false, 272 | }; 273 | 274 | match app.get("Entitlements") { 275 | Some(plist::Value::Dictionary(entitlements)) => { 276 | matches!( 277 | entitlements.get("get-task-allow"), 278 | Some(plist::Value::Boolean(true)) 279 | ) 280 | } 281 | _ => false, 282 | } 283 | }) 284 | .map(|(bundle_id, app)| { 285 | let name = match app { 286 | plist::Value::Dictionary(mut d) => match d.remove("CFBundleName") { 287 | Some(plist::Value::String(bundle_name)) => bundle_name, 288 | _ => bundle_id.clone(), 289 | }, 290 | _ => bundle_id.clone(), 291 | }; 292 | (name.clone(), bundle_id) 293 | }) 294 | .collect(); 295 | 296 | if apps.is_empty() { 297 | return Json(GetAppsReturn { 298 | ok: false, 299 | apps: Vec::new(), 300 | bundle_ids: None, 301 | error: Some("No apps with get-task-allow found".to_string()), 302 | }); 303 | } 304 | 305 | apps.insert("Other...".to_string(), "UPDATE YOUR SHORTCUT".to_string()); 306 | 307 | state 308 | .new_heartbeat_sender 309 | .send(heartbeat::SendRequest::Kill(udid.clone())) 310 | .await 311 | .unwrap(); 312 | 313 | Json(GetAppsReturn { 314 | ok: true, 315 | apps: apps.keys().map(|x| x.to_string()).collect(), 316 | bundle_ids: Some(apps), 317 | error: None, 318 | }) 319 | } 320 | 321 | #[derive(Serialize, Deserialize)] 322 | struct LaunchAppReturn { 323 | ok: bool, 324 | launching: bool, 325 | position: Option, 326 | error: Option, 327 | mounting: bool, // NOTICE: this field does literally nothing and will be removed in future 328 | // versions 329 | } 330 | 331 | /// - Get the IP from the request and UDID from the database 332 | /// - Mount the device 333 | /// - Connect to tunneld and get the interface and port for the developer service 334 | /// - Send the commands to launch the app and detach 335 | /// - Set last_used to now in the database 336 | async fn launch_app( 337 | ip: SecureClientIp, 338 | Path(bundle_id): Path, 339 | State(state): State, 340 | ) -> Json { 341 | let ip = ip.0; 342 | 343 | info!("Got request to launch {bundle_id} from {:?}", ip); 344 | 345 | let udid = match common::get_udid_from_ip(ip.to_string()).await { 346 | Ok(u) => u, 347 | Err(e) => { 348 | return Json(LaunchAppReturn { 349 | ok: false, 350 | error: Some(e), 351 | launching: false, 352 | position: None, 353 | mounting: false, 354 | }) 355 | } 356 | }; 357 | 358 | // Get the pairing file 359 | debug!("Getting pairing file for {udid}"); 360 | let pairing_file = match get_pairing_file(&udid, &state.pairing_file_storage).await { 361 | Ok(pairing_file) => pairing_file, 362 | Err(e) => { 363 | info!("Failed to get pairing file: {:?}", e); 364 | return Json(LaunchAppReturn { 365 | ok: false, 366 | launching: false, 367 | position: None, 368 | mounting: false, 369 | error: Some(format!("Failed to get pairing file: {:?}", e)), 370 | }); 371 | } 372 | }; 373 | 374 | // Heartbeat the device 375 | match heartbeat::heartbeat_thread(udid.clone(), ip, &pairing_file).await { 376 | Ok(s) => { 377 | state 378 | .new_heartbeat_sender 379 | .send(heartbeat::SendRequest::Store((udid.clone(), s))) 380 | .await 381 | .unwrap(); 382 | } 383 | Err(e) => { 384 | let e = match e { 385 | idevice::IdeviceError::InvalidHostID => { 386 | "your pairing file is invalid. Regenerate it with jitterbug pair.".to_string() 387 | } 388 | _ => e.to_string(), 389 | }; 390 | info!("Failed to heartbeat device: {:?}", e); 391 | return Json(LaunchAppReturn { 392 | ok: false, 393 | launching: false, 394 | position: None, 395 | mounting: false, 396 | error: Some(format!("Failed to heartbeat device: {e}")), 397 | }); 398 | } 399 | } 400 | 401 | let provider = TcpProvider { 402 | addr: ip, 403 | pairing_file, 404 | label: "JitStreamer-EB".to_string(), 405 | }; 406 | 407 | let proxy = match CoreDeviceProxy::connect(&provider).await { 408 | Ok(p) => p, 409 | Err(e) => { 410 | info!("Failed to proxy device: {:?}", e); 411 | return Json(LaunchAppReturn { 412 | ok: false, 413 | launching: false, 414 | position: None, 415 | mounting: false, 416 | error: Some(format!("Failed to start core device proxy: {e}")), 417 | }); 418 | } 419 | }; 420 | let rsd_port = proxy.handshake.server_rsd_port; 421 | let mut adapter = match proxy.create_software_tunnel() { 422 | Ok(a) => a, 423 | Err(e) => { 424 | info!("Failed to create software tunnel: {:?}", e); 425 | return Json(LaunchAppReturn { 426 | ok: false, 427 | launching: false, 428 | position: None, 429 | mounting: false, 430 | error: Some(format!("Failed to create software tunnel: {e}")), 431 | }); 432 | } 433 | }; 434 | 435 | if let Err(e) = adapter.connect(rsd_port).await { 436 | info!("Failed to connect to RemoteXPC port: {:?}", e); 437 | return Json(LaunchAppReturn { 438 | ok: false, 439 | launching: false, 440 | position: None, 441 | mounting: false, 442 | error: Some(format!("Failed to connect to RemoteXPC port: {e}")), 443 | }); 444 | } 445 | 446 | let xpc_client = match idevice::xpc::XPCDevice::new(adapter).await { 447 | Ok(x) => x, 448 | Err(e) => { 449 | log::warn!("Failed to connect to RemoteXPC: {e:?}"); 450 | return Json(LaunchAppReturn { 451 | ok: false, 452 | error: Some("Failed to connect to RemoteXPC".to_string()), 453 | launching: false, 454 | position: None, 455 | mounting: false, 456 | }); 457 | } 458 | }; 459 | 460 | let dvt_port = match xpc_client.services.get(idevice::dvt::SERVICE_NAME) { 461 | Some(s) => s.port, 462 | None => { 463 | return Json(LaunchAppReturn { 464 | ok: false, 465 | error: Some( 466 | "Device did not contain DVT service. Is the image mounted?".to_string(), 467 | ), 468 | launching: false, 469 | position: None, 470 | mounting: false, 471 | }); 472 | } 473 | }; 474 | let debug_proxy_port = match xpc_client.services.get(idevice::debug_proxy::SERVICE_NAME) { 475 | Some(s) => s.port, 476 | None => { 477 | return Json(LaunchAppReturn { 478 | ok: false, 479 | error: Some( 480 | "Device did not contain debug server service. Is the image mounted?" 481 | .to_string(), 482 | ), 483 | launching: false, 484 | position: None, 485 | mounting: false, 486 | }); 487 | } 488 | }; 489 | 490 | let mut adapter = xpc_client.into_inner(); 491 | if let Err(e) = adapter.close().await { 492 | log::warn!("Failed to close RemoteXPC port: {e:?}"); 493 | return Json(LaunchAppReturn { 494 | ok: false, 495 | error: Some("Failed to close RemoteXPC port".to_string()), 496 | launching: false, 497 | position: None, 498 | mounting: false, 499 | }); 500 | } 501 | 502 | info!("Connecting to DVT port"); 503 | if let Err(e) = adapter.connect(dvt_port).await { 504 | log::warn!("Failed to connect to DVT port: {e:?}"); 505 | return Json(LaunchAppReturn { 506 | ok: false, 507 | error: Some("Failed to connect to DVT port".to_string()), 508 | launching: false, 509 | position: None, 510 | mounting: false, 511 | }); 512 | } 513 | 514 | let mut rs_client = match idevice::dvt::remote_server::RemoteServerClient::new(adapter) { 515 | Ok(r) => r, 516 | Err(e) => { 517 | log::warn!("Failed to create remote server client: {e:?}"); 518 | return Json(LaunchAppReturn { 519 | ok: false, 520 | error: Some(format!("Failed to create remote server client: {e:?}")), 521 | launching: false, 522 | position: None, 523 | mounting: false, 524 | }); 525 | } 526 | }; 527 | if let Err(e) = rs_client.read_message(0).await { 528 | log::warn!("Failed to read first message from remote server client: {e:?}"); 529 | return Json(LaunchAppReturn { 530 | ok: false, 531 | error: Some(format!( 532 | "Failed to read first message from remote server client: {e:?}" 533 | )), 534 | launching: false, 535 | position: None, 536 | mounting: false, 537 | }); 538 | } 539 | 540 | let mut pc_client = 541 | match idevice::dvt::process_control::ProcessControlClient::new(&mut rs_client).await { 542 | Ok(p) => p, 543 | Err(e) => { 544 | log::warn!("Failed to create process control client: {e:?}"); 545 | return Json(LaunchAppReturn { 546 | ok: false, 547 | error: Some(format!("Failed to create process control client: {e:?}")), 548 | launching: false, 549 | position: None, 550 | mounting: false, 551 | }); 552 | } 553 | }; 554 | 555 | let pid = match pc_client 556 | .launch_app(bundle_id, None, None, true, false) 557 | .await 558 | { 559 | Ok(p) => p, 560 | Err(e) => { 561 | log::warn!("Failed to launch app: {e:?}"); 562 | return Json(LaunchAppReturn { 563 | ok: false, 564 | error: Some(format!("Failed to launch app: {e:?}")), 565 | launching: false, 566 | position: None, 567 | mounting: false, 568 | }); 569 | } 570 | }; 571 | debug!("Launched app with PID {pid}"); 572 | if let Err(e) = pc_client.disable_memory_limit(pid).await { 573 | log::warn!("Failed to disable memory limit: {e:?}") 574 | } 575 | 576 | let mut adapter = rs_client.into_inner(); 577 | if let Err(e) = adapter.close().await { 578 | log::warn!("Failed to close DVT port: {e:?}"); 579 | return Json(LaunchAppReturn { 580 | ok: false, 581 | error: Some("Failed to close RemoteXPC port".to_string()), 582 | launching: false, 583 | position: None, 584 | mounting: false, 585 | }); 586 | } 587 | 588 | info!("Connecting to debug proxy port: {debug_proxy_port}"); 589 | if let Err(e) = adapter.connect(debug_proxy_port).await { 590 | log::warn!("Failed to connect to debug proxy port: {e:?}"); 591 | return Json(LaunchAppReturn { 592 | ok: false, 593 | error: Some("Failed to connect to debug proxy port".to_string()), 594 | launching: false, 595 | position: None, 596 | mounting: false, 597 | }); 598 | } 599 | 600 | let mut dp = DebugProxyClient::new(adapter); 601 | let commands = [ 602 | format!("vAttach;{pid:02X}"), 603 | "D".to_string(), 604 | "D".to_string(), 605 | "D".to_string(), 606 | "D".to_string(), 607 | ]; 608 | for command in commands { 609 | match dp.send_command(command.into()).await { 610 | Ok(res) => { 611 | debug!("command res: {res:?}"); 612 | } 613 | Err(e) => { 614 | log::warn!("Failed to send command to debug server: {e:?}"); 615 | return Json(LaunchAppReturn { 616 | ok: false, 617 | error: Some(format!("Failed to send command to debug server: {e:?}")), 618 | launching: false, 619 | position: None, 620 | mounting: false, 621 | }); 622 | } 623 | } 624 | } 625 | 626 | debug!("JIT finished, killing heartbeat"); 627 | state 628 | .new_heartbeat_sender 629 | .send(heartbeat::SendRequest::Kill(udid.clone())) 630 | .await 631 | .unwrap(); 632 | 633 | Json(LaunchAppReturn { 634 | ok: true, 635 | error: None, 636 | launching: true, // true for compatibility reasons, will be removed 637 | position: Some(0), // compat field 638 | mounting: false, 639 | }) 640 | } 641 | 642 | // compat with OG JitStreamer 643 | #[derive(Debug, Serialize)] 644 | struct AttachReturn { 645 | success: bool, 646 | message: String, 647 | } 648 | 649 | impl AttachReturn { 650 | fn fail(message: String) -> Self { 651 | Self { 652 | success: false, 653 | message, 654 | } 655 | } 656 | } 657 | 658 | async fn attach_app( 659 | ip: SecureClientIp, 660 | Path(pid): Path, 661 | State(state): State, 662 | ) -> Json { 663 | let ip = ip.0; 664 | 665 | info!("Got request to attach {pid} from {:?}", ip); 666 | 667 | let udid = match common::get_udid_from_ip(ip.to_string()).await { 668 | Ok(u) => u, 669 | Err(e) => return Json(AttachReturn::fail(e)), 670 | }; 671 | 672 | // Get the pairing file 673 | debug!("Getting pairing file for {udid}"); 674 | let pairing_file = match get_pairing_file(&udid, &state.pairing_file_storage).await { 675 | Ok(pairing_file) => pairing_file, 676 | Err(e) => { 677 | info!("Failed to get pairing file: {:?}", e); 678 | return Json(AttachReturn::fail(format!( 679 | "Failed to get pairing file: {:?}", 680 | e 681 | ))); 682 | } 683 | }; 684 | 685 | // Heartbeat the device 686 | match heartbeat::heartbeat_thread(udid.clone(), ip, &pairing_file).await { 687 | Ok(s) => { 688 | state 689 | .new_heartbeat_sender 690 | .send(heartbeat::SendRequest::Store((udid.clone(), s))) 691 | .await 692 | .unwrap(); 693 | } 694 | Err(e) => { 695 | let e = match e { 696 | idevice::IdeviceError::InvalidHostID => { 697 | "your pairing file is invalid. Regenerate it with jitterbug pair.".to_string() 698 | } 699 | _ => e.to_string(), 700 | }; 701 | info!("Failed to heartbeat device: {:?}", e); 702 | return Json(AttachReturn::fail(format!( 703 | "Failed to heartbeat device: {e}" 704 | ))); 705 | } 706 | } 707 | 708 | let provider = TcpProvider { 709 | addr: ip, 710 | pairing_file, 711 | label: "JitStreamer-EB".to_string(), 712 | }; 713 | 714 | let proxy = match CoreDeviceProxy::connect(&provider).await { 715 | Ok(p) => p, 716 | Err(e) => { 717 | info!("Failed to proxy device: {:?}", e); 718 | return Json(AttachReturn::fail(format!( 719 | "Failed to start core device proxy: {e}" 720 | ))); 721 | } 722 | }; 723 | let rsd_port = proxy.handshake.server_rsd_port; 724 | let mut adapter = match proxy.create_software_tunnel() { 725 | Ok(a) => a, 726 | Err(e) => { 727 | info!("Failed to create software tunnel: {:?}", e); 728 | return Json(AttachReturn::fail(format!( 729 | "Failed to create software tunnel: {e}" 730 | ))); 731 | } 732 | }; 733 | if let Err(e) = adapter.connect(rsd_port).await { 734 | info!("Failed to connect to RemoteXPC port: {:?}", e); 735 | return Json(AttachReturn::fail(format!( 736 | "Failed to connect to RemoteXPC port: {e}" 737 | ))); 738 | } 739 | 740 | let xpc_client = match idevice::xpc::XPCDevice::new(adapter).await { 741 | Ok(x) => x, 742 | Err(e) => { 743 | log::warn!("Failed to connect to RemoteXPC: {e:?}"); 744 | return Json(AttachReturn::fail( 745 | "Failed to connect to RemoteXPC".to_string(), 746 | )); 747 | } 748 | }; 749 | 750 | let service_port = match xpc_client.services.get(idevice::debug_proxy::SERVICE_NAME) { 751 | Some(s) => s.port, 752 | None => { 753 | return Json(AttachReturn::fail( 754 | "Device did not contain debug server service. Is the image mounted?".to_string(), 755 | )); 756 | } 757 | }; 758 | 759 | let mut adapter = xpc_client.into_inner(); 760 | if let Err(e) = adapter.close().await { 761 | log::warn!("Failed to close RemoteXPC port: {e:?}"); 762 | return Json(AttachReturn::fail(format!( 763 | "Failed to close RemoteXPC port: {e:?}" 764 | ))); 765 | } 766 | if let Err(e) = adapter.connect(service_port).await { 767 | log::warn!("Failed to connect to debug proxy port: {e:?}"); 768 | return Json(AttachReturn::fail(format!( 769 | "Failed to connect to debug proxy port: {e:?}" 770 | ))); 771 | } 772 | 773 | let mut dp = DebugProxyClient::new(adapter); 774 | let commands = [format!("vAttach;{pid:02X}"), "D".to_string()]; 775 | for command in commands { 776 | match dp.send_command(command.into()).await { 777 | Ok(res) => { 778 | debug!("command res: {res:?}"); 779 | } 780 | Err(e) => { 781 | log::warn!("Failed to send command to debug server: {e:?}"); 782 | return Json(AttachReturn::fail(format!( 783 | "Failed to send command to debug server: {e:?}" 784 | ))); 785 | } 786 | } 787 | } 788 | 789 | state 790 | .new_heartbeat_sender 791 | .send(heartbeat::SendRequest::Kill(udid.clone())) 792 | .await 793 | .unwrap(); 794 | 795 | Json(AttachReturn { 796 | success: true, 797 | message: "".to_string(), 798 | }) 799 | } 800 | 801 | #[derive(Debug, Serialize)] 802 | struct StatusReturn { 803 | done: bool, 804 | ok: bool, 805 | position: usize, 806 | error: Option, 807 | in_progress: bool, // NOTICE: this field is deprecated and will be removed in future versions 808 | } 809 | 810 | /// Stub function to remain compatible with dependant apps 811 | /// Will be removed in future updates 812 | async fn status() -> Json { 813 | Json(StatusReturn { 814 | ok: true, 815 | done: true, 816 | position: 0, 817 | error: None, 818 | in_progress: false, 819 | }) 820 | } 821 | -------------------------------------------------------------------------------- /src/mount.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Device Mounting 9 | 35 | 36 | 37 |

Your device is mounting. Please wait.

38 |
Waiting for data...
39 | 40 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /src/mount.rs: -------------------------------------------------------------------------------- 1 | // Jackson Coxson 2 | 3 | use std::{collections::HashMap, sync::Arc}; 4 | 5 | use axum::{ 6 | extract::{ 7 | ws::{Message, WebSocket}, 8 | State, WebSocketUpgrade, 9 | }, 10 | Json, 11 | }; 12 | use axum_client_ip::SecureClientIp; 13 | use idevice::{ 14 | lockdownd::LockdowndClient, 15 | mounter::ImageMounter, 16 | provider::{IdeviceProvider, TcpProvider}, 17 | IdeviceError, IdeviceService, 18 | }; 19 | use log::{debug, info, warn}; 20 | use serde::Serialize; 21 | use tokio::sync::{watch, Mutex}; 22 | 23 | use crate::{ 24 | common, 25 | heartbeat::{self, NewHeartbeatSender}, 26 | JitStreamerState, 27 | }; 28 | 29 | const BUILD_MANIFEST: &[u8] = include_bytes!("../DDI/BuildManifest.plist"); 30 | const DDI_IMAGE: &[u8] = include_bytes!("../DDI/Image.dmg"); 31 | const DDI_TRUSTCACHE: &[u8] = include_bytes!("../DDI/Image.dmg.trustcache"); 32 | 33 | pub type MountCache = 34 | Arc>>>>; 35 | 36 | #[derive(Serialize)] 37 | pub struct CheckMountResponse { 38 | ok: bool, 39 | error: Option, 40 | mounting: bool, 41 | } 42 | 43 | #[derive(Serialize)] 44 | pub struct MountWebSocketMessage { 45 | ok: bool, 46 | percentage: f32, 47 | error: Option, 48 | done: bool, 49 | } 50 | 51 | pub async fn check_mount( 52 | ip: SecureClientIp, 53 | State(state): State, 54 | ) -> Json { 55 | let udid = match common::get_udid_from_ip(ip.0.to_string()).await { 56 | Ok(u) => u, 57 | Err(e) => { 58 | return Json(CheckMountResponse { 59 | ok: false, 60 | error: Some(e), 61 | mounting: false, 62 | }); 63 | } 64 | }; 65 | 66 | let mut lock = state.mount_cache.lock().await; 67 | if let Some(i) = lock.get(&udid) { 68 | let i = i.borrow().clone(); 69 | match i { 70 | Ok((_, _, complete)) => { 71 | if complete { 72 | lock.remove(&udid); 73 | return Json(CheckMountResponse { 74 | ok: true, 75 | error: None, 76 | mounting: false, 77 | }); 78 | } 79 | } 80 | Err(e) => { 81 | lock.remove(&udid); 82 | return Json(CheckMountResponse { 83 | ok: false, 84 | error: Some(format!("Failed to mount image: {e}")), 85 | mounting: false, 86 | }); 87 | } 88 | } 89 | debug!("Device {udid} is already mounting"); 90 | return Json(CheckMountResponse { 91 | ok: true, 92 | error: None, 93 | mounting: true, 94 | }); 95 | } 96 | std::mem::drop(lock); 97 | 98 | let pairing_file = match common::get_pairing_file(&udid, &state.pairing_file_storage).await { 99 | Ok(p) => p, 100 | Err(e) => { 101 | return Json(CheckMountResponse { 102 | ok: false, 103 | mounting: false, 104 | error: Some(format!("Unable to get pairing file: {e}")), 105 | }) 106 | } 107 | }; 108 | 109 | // Start a heartbeat, get the list of images 110 | match heartbeat::heartbeat_thread(udid.clone(), ip.0, &pairing_file).await { 111 | Ok(s) => { 112 | state 113 | .new_heartbeat_sender 114 | .send(heartbeat::SendRequest::Store((udid.clone(), s))) 115 | .await 116 | .unwrap(); 117 | } 118 | Err(e) => { 119 | let e = match e { 120 | idevice::IdeviceError::InvalidHostID => { 121 | "your pairing file is invalid. Regenerate it with jitterbug pair.".to_string() 122 | } 123 | _ => e.to_string(), 124 | }; 125 | info!("Failed to heartbeat device: {:?}", e); 126 | return Json(CheckMountResponse { 127 | ok: false, 128 | mounting: false, 129 | error: Some(format!("Failed to heartbeat device: {e}")), 130 | }); 131 | } 132 | } 133 | 134 | // Get the list of mounted images 135 | let provider = TcpProvider { 136 | addr: ip.0, 137 | pairing_file, 138 | label: "JitStreamer-EB".to_string(), 139 | }; 140 | 141 | let mut mounter_client = match ImageMounter::connect(&provider).await { 142 | Ok(m) => m, 143 | Err(e) => { 144 | return Json(CheckMountResponse { 145 | ok: false, 146 | mounting: false, 147 | error: Some(format!("Failed to start image mounter: {e:?}")), 148 | }) 149 | } 150 | }; 151 | 152 | let images = match mounter_client.copy_devices().await { 153 | Ok(images) => images, 154 | Err(e) => { 155 | info!("Failed to get images: {:?}", e); 156 | return Json(CheckMountResponse { 157 | ok: false, 158 | mounting: false, 159 | error: Some(format!("Failed to get images: {:?}", e)), 160 | }); 161 | } 162 | }; 163 | 164 | let mut mounted = false; 165 | for image in images { 166 | let mut buf = Vec::new(); 167 | let mut writer = std::io::Cursor::new(&mut buf); 168 | plist::to_writer_xml(&mut writer, &image).unwrap(); 169 | 170 | let image = String::from_utf8_lossy(&buf); 171 | if image.contains("Developer") { 172 | mounted = true; 173 | break; 174 | } 175 | } 176 | 177 | if mounted { 178 | Json(CheckMountResponse { 179 | ok: true, 180 | error: None, 181 | mounting: false, 182 | }) 183 | } else { 184 | let (sw, rw) = watch::channel(Ok((0, 100, false))); 185 | mount_thread( 186 | provider, 187 | sw, 188 | state.new_heartbeat_sender.clone(), 189 | udid.clone(), 190 | ); 191 | state.mount_cache.lock().await.insert(udid, rw); 192 | 193 | Json(CheckMountResponse { 194 | ok: true, 195 | error: None, 196 | mounting: true, 197 | }) 198 | } 199 | } 200 | 201 | fn mount_thread( 202 | provider: TcpProvider, 203 | sender: watch::Sender>, 204 | hb: NewHeartbeatSender, 205 | udid: String, 206 | ) { 207 | debug!("Starting mount thread for {udid}"); 208 | tokio::task::spawn(async move { 209 | // Start work in a new fuction so we can use ? 210 | async fn work( 211 | provider: TcpProvider, 212 | sender: watch::Sender>, 213 | hb: NewHeartbeatSender, 214 | udid: String, 215 | ) -> Result<(), IdeviceError> { 216 | debug!("Getting chip ID for {udid}"); 217 | let mut lockdown_client = LockdowndClient::connect(&provider).await?; 218 | lockdown_client 219 | .start_session(&provider.get_pairing_file().await?) 220 | .await?; 221 | 222 | let unique_chip_id = match lockdown_client 223 | .get_value("UniqueChipID") 224 | .await? 225 | .as_unsigned_integer() 226 | { 227 | Some(u) => u, 228 | None => { 229 | return Err(IdeviceError::UnexpectedResponse); 230 | } 231 | }; 232 | 233 | let mut mounter_client = ImageMounter::connect(&provider).await?; 234 | mounter_client 235 | .mount_personalized_with_callback( 236 | &provider, 237 | DDI_IMAGE.to_vec(), 238 | DDI_TRUSTCACHE.to_vec(), 239 | BUILD_MANIFEST, 240 | None, 241 | unique_chip_id, 242 | |(progress, state)| async move { 243 | state.clone().send(Ok((progress.0, progress.1, false))).ok(); 244 | }, 245 | sender, 246 | ) 247 | .await?; 248 | hb.send(crate::heartbeat::SendRequest::Kill(udid)) 249 | .await 250 | .ok(); 251 | Ok(()) 252 | } 253 | if let Err(e) = work(provider, sender.clone(), hb, udid.clone()).await { 254 | warn!("Failed to mount for {udid}: {e:?}"); 255 | sender.send(Err(e.to_string())).ok(); 256 | } else { 257 | sender.send(Ok((1, 1, true))).ok(); 258 | } 259 | }); 260 | } 261 | 262 | pub async fn handler( 263 | ws: WebSocketUpgrade, 264 | ip: SecureClientIp, 265 | State(state): State, 266 | ) -> axum::response::Response { 267 | let ip = ip.0.to_string(); 268 | ws.on_upgrade(|s| async move { handle_socket(s, ip.clone(), state).await }) 269 | } 270 | 271 | async fn handle_socket(mut socket: WebSocket, ip: String, state: JitStreamerState) { 272 | let udid = match common::get_udid_from_ip(ip).await { 273 | Ok(u) => u, 274 | Err(e) => { 275 | socket 276 | .send( 277 | MountWebSocketMessage { 278 | ok: false, 279 | percentage: 0.0, 280 | error: Some(e), 281 | done: false, 282 | } 283 | .to_ws_message(), 284 | ) 285 | .await 286 | .ok(); 287 | return; 288 | } 289 | }; 290 | 291 | let lock = state.mount_cache.lock().await; 292 | let mut receiver = match lock.get(&udid) { 293 | Some(r) => r.clone(), 294 | None => { 295 | socket 296 | .send( 297 | MountWebSocketMessage { 298 | ok: true, 299 | error: None, 300 | percentage: 0.0, 301 | done: false, 302 | } 303 | .to_ws_message(), 304 | ) 305 | .await 306 | .ok(); 307 | return; 308 | } 309 | }; 310 | std::mem::drop(lock); 311 | 312 | loop { 313 | let msg = receiver.borrow().clone(); 314 | if match msg { 315 | Ok((a, b, complete)) => socket.send( 316 | MountWebSocketMessage { 317 | ok: true, 318 | error: None, 319 | percentage: a as f32 / b as f32, 320 | done: complete, 321 | } 322 | .to_ws_message(), 323 | ), 324 | Err(e) => socket.send( 325 | MountWebSocketMessage { 326 | ok: false, 327 | error: Some(e), 328 | percentage: 0.0, 329 | done: false, 330 | } 331 | .to_ws_message(), 332 | ), 333 | } 334 | .await 335 | .is_err() 336 | { 337 | debug!("Failed to send status to websocket"); 338 | return; 339 | } 340 | 341 | if receiver.changed().await.is_err() { 342 | debug!("Receiver failed to recv msg"); 343 | return; 344 | } 345 | } 346 | } 347 | 348 | impl MountWebSocketMessage { 349 | fn to_ws_message(&self) -> Message { 350 | Message::text(serde_json::to_string(&self).unwrap()) 351 | } 352 | } 353 | -------------------------------------------------------------------------------- /src/netmuxd.rs: -------------------------------------------------------------------------------- 1 | // Jackson Coxson 2 | 3 | use std::net::IpAddr; 4 | 5 | use log::error; 6 | use tokio::{ 7 | io::{AsyncReadExt, AsyncWriteExt}, 8 | net::UnixStream, 9 | }; 10 | 11 | use crate::raw_packet; 12 | 13 | const NETMUXD_SOCKET: &str = "/var/run/usbmuxd"; 14 | const SERVICE_NAME: &str = "apple-mobdev2"; 15 | const SERVICE_PROTOCOL: &str = "tcp"; 16 | 17 | /// Connects to the unix socket and adds the device 18 | pub async fn add_device(ip: IpAddr, udid: &str) -> bool { 19 | let mut stream = UnixStream::connect(NETMUXD_SOCKET) 20 | .await 21 | .expect("Could not connect to netmuxd socket, is it running?"); 22 | 23 | let mut request = plist::Dictionary::new(); 24 | request.insert("MessageType".into(), "AddDevice".into()); 25 | request.insert("ConnectionType".into(), "Network".into()); 26 | request.insert( 27 | "ServiceName".into(), 28 | format!("_{}._{}.local", SERVICE_NAME, SERVICE_PROTOCOL).into(), 29 | ); 30 | request.insert("IPAddress".into(), ip.to_string().into()); 31 | request.insert("DeviceID".into(), udid.into()); 32 | 33 | let request = raw_packet::RawPacket::new(request, 69, 69, 69); 34 | let request: Vec = request.into(); 35 | 36 | stream.write_all(&request).await.unwrap(); 37 | 38 | let mut buf = Vec::new(); 39 | let size = stream.read_to_end(&mut buf).await.unwrap(); 40 | 41 | let buffer = &mut buf[0..size].to_vec(); 42 | if size == 16 { 43 | let packet_size = &buffer[0..4]; 44 | let packet_size = u32::from_le_bytes(packet_size.try_into().unwrap()); 45 | // Pull the rest of the packet 46 | let mut packet = vec![0; packet_size as usize]; 47 | let _ = stream.read(&mut packet).await.unwrap(); 48 | // Append the packet to the buffer 49 | buffer.append(&mut packet); 50 | } 51 | 52 | let parsed: raw_packet::RawPacket = match buffer.try_into() { 53 | Ok(p) => p, 54 | Err(_) => { 55 | log::error!("Failed to parse response as usbmuxd packet!!"); 56 | return false; 57 | } 58 | }; 59 | match parsed.plist.get("Result") { 60 | Some(plist::Value::Integer(r)) => r.as_unsigned().unwrap() == 1, 61 | _ => false, 62 | } 63 | } 64 | 65 | pub async fn remove_device(udid: &str) { 66 | let mut stream = UnixStream::connect(NETMUXD_SOCKET) 67 | .await 68 | .expect("Could not connect to netmuxd socket, is it running?"); 69 | 70 | let mut request = plist::Dictionary::new(); 71 | request.insert("MessageType".into(), "RemoveDevice".into()); 72 | request.insert("DeviceID".into(), udid.into()); 73 | 74 | let request = raw_packet::RawPacket::new(request, 69, 69, 69); 75 | let request: Vec = request.into(); 76 | 77 | if let Err(e) = stream.write_all(&request).await { 78 | error!("Error writing to netmuxd socket: {}", e); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/raw_packet.rs: -------------------------------------------------------------------------------- 1 | // jkcoxson - excerpt from netmuxd 2 | 3 | use log::warn; 4 | 5 | #[derive(Debug)] 6 | pub struct RawPacket { 7 | pub size: u32, 8 | pub version: u32, 9 | pub message: u32, 10 | pub tag: u32, 11 | pub plist: plist::Dictionary, 12 | } 13 | 14 | fn plist_to_bytes(p: &plist::Dictionary) -> Vec { 15 | let buf = Vec::new(); 16 | let mut writer = std::io::BufWriter::new(buf); 17 | plist::to_writer_xml(&mut writer, &p).unwrap(); 18 | 19 | writer.into_inner().unwrap() 20 | } 21 | 22 | impl RawPacket { 23 | pub fn new(plist: plist::Dictionary, version: u32, message: u32, tag: u32) -> RawPacket { 24 | let plist_bytes = plist_to_bytes(&plist); 25 | let size = plist_bytes.len() as u32 + 16; 26 | RawPacket { 27 | size, 28 | version, 29 | message, 30 | tag, 31 | plist, 32 | } 33 | } 34 | } 35 | 36 | impl From for Vec { 37 | fn from(raw_packet: RawPacket) -> Vec { 38 | let mut packet = vec![]; 39 | packet.extend_from_slice(&raw_packet.size.to_le_bytes()); 40 | packet.extend_from_slice(&raw_packet.version.to_le_bytes()); 41 | packet.extend_from_slice(&raw_packet.message.to_le_bytes()); 42 | packet.extend_from_slice(&raw_packet.tag.to_le_bytes()); 43 | packet.extend_from_slice(&plist_to_bytes(&raw_packet.plist)); 44 | packet 45 | } 46 | } 47 | 48 | impl TryFrom<&mut Vec> for RawPacket { 49 | type Error = (); 50 | fn try_from(packet: &mut Vec) -> Result { 51 | let packet: &[u8] = packet; 52 | packet.try_into() 53 | } 54 | } 55 | 56 | impl TryFrom<&[u8]> for RawPacket { 57 | type Error = (); 58 | fn try_from(packet: &[u8]) -> Result { 59 | // Determine if we have enough data to parse 60 | if packet.len() < 16 { 61 | warn!("Not enough data to parse a raw packet header"); 62 | return Err(()); 63 | } 64 | 65 | let packet_size = &packet[0..4]; 66 | let packet_size = u32::from_le_bytes(match packet_size.try_into() { 67 | Ok(packet_size) => packet_size, 68 | Err(_) => { 69 | warn!("Failed to parse packet size"); 70 | return Err(()); 71 | } 72 | }); 73 | 74 | // Determine if we have enough data to parse 75 | if packet.len() < packet_size as usize { 76 | warn!("Not enough data to parse a raw packet body"); 77 | return Err(()); 78 | } 79 | 80 | let packet_version = &packet[4..8]; 81 | let packet_version = u32::from_le_bytes(match packet_version.try_into() { 82 | Ok(packet_version) => packet_version, 83 | Err(_) => { 84 | warn!("Failed to parse packet version"); 85 | return Err(()); 86 | } 87 | }); 88 | 89 | let message = &packet[8..12]; 90 | let message = u32::from_le_bytes(match message.try_into() { 91 | Ok(message) => message, 92 | Err(_) => { 93 | warn!("Failed to parse packet message"); 94 | return Err(()); 95 | } 96 | }); 97 | 98 | let packet_tag = &packet[12..16]; 99 | let packet_tag = u32::from_le_bytes(match packet_tag.try_into() { 100 | Ok(packet_tag) => packet_tag, 101 | Err(_) => { 102 | warn!("Failed to parse packet tag"); 103 | return Err(()); 104 | } 105 | }); 106 | 107 | let plist = &packet[16..packet_size as usize]; 108 | let plist = if let Ok(p) = plist::from_bytes(plist) { 109 | p 110 | } else { 111 | warn!("Failed to parse packet plist"); 112 | return Err(()); 113 | }; 114 | 115 | Ok(RawPacket { 116 | size: packet_size, 117 | version: packet_version, 118 | message, 119 | tag: packet_tag, 120 | plist, 121 | }) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/register.rs: -------------------------------------------------------------------------------- 1 | // Jackson Coxson 2 | 3 | use axum::{body::Bytes, http::StatusCode, response::Html}; 4 | use axum_client_ip::SecureClientIp; 5 | use log::info; 6 | use plist::Dictionary; 7 | use sha2::Digest; 8 | use sqlite::State; 9 | use std::net::{IpAddr, Ipv6Addr}; 10 | 11 | /// Check to make sure the Wireguard interface exists 12 | pub fn check_wireguard() { 13 | let wireguard_config_name = 14 | std::env::var("WIREGUARD_CONFIG_NAME").unwrap_or("jitstreamer".to_string()); 15 | let wireguard_conf = format!("/etc/wireguard/{wireguard_config_name}.conf"); 16 | let wireguard_port = std::env::var("WIREGUARD_PORT") 17 | .unwrap_or("51869".to_string()) 18 | .parse::() 19 | .unwrap_or(51869); 20 | let wireguard_server_address = 21 | std::env::var("WIREGUARD_SERVER_ADDRESS").unwrap_or("fd00::/128".to_string()); 22 | 23 | if !std::fs::exists(&wireguard_conf).unwrap() { 24 | let key = wg_config::WgKey::generate_private_key().expect("failed to generate key"); 25 | let interface = wg_config::WgInterface::new( 26 | key, 27 | wireguard_server_address.parse().unwrap(), 28 | Some(wireguard_port), 29 | None, 30 | None, 31 | None, 32 | ) 33 | .unwrap(); 34 | 35 | wg_config::WgConf::create(wireguard_conf.as_str(), interface, None) 36 | .expect("failed to create config"); 37 | 38 | info!("Created new Wireguard config"); 39 | 40 | // Run wg-quick up jitstreamer 41 | let _ = std::process::Command::new("bash") 42 | .arg("-c") 43 | .arg(format!("wg-quick up {wireguard_config_name}")) 44 | .output() 45 | .expect("failed to execute process"); 46 | } 47 | } 48 | 49 | /// Takes the plist in bytes, and returns either the pairing file in return or an error message 50 | pub async fn register( 51 | client_ip: SecureClientIp, 52 | plist_bytes: Bytes, 53 | ) -> Result { 54 | let plist = match plist::from_bytes::(plist_bytes.as_ref()) { 55 | Ok(plist) => plist, 56 | Err(_) => return Err((StatusCode::BAD_REQUEST, "bad plist")), 57 | }; 58 | let udid = match plist.get("UDID") { 59 | Some(plist::Value::String(udid)) => udid, 60 | _ => return Err((StatusCode::BAD_REQUEST, "no UDID")), 61 | } 62 | .to_owned(); 63 | 64 | let cloned_udid = udid.clone(); 65 | // Reverse lookup the device to see if we already have an IP for it 66 | let ip = match tokio::task::spawn_blocking(move || { 67 | let db = match sqlite::open("jitstreamer.db") { 68 | Ok(db) => db, 69 | Err(e) => { 70 | info!("Failed to open database: {:?}", e); 71 | return None; 72 | } 73 | }; 74 | 75 | // Get the device from the database 76 | let query = "SELECT ip FROM devices WHERE udid = ?"; 77 | let mut statement = match crate::db::db_prepare(&db, query) { 78 | Some(s) => s, 79 | None => { 80 | log::error!("Failed to prepare query!"); 81 | return None; 82 | } 83 | }; 84 | statement 85 | .bind((1, cloned_udid.to_string().as_str())) 86 | .unwrap(); 87 | if let Some(State::Row) = crate::db::statement_next(&mut statement) { 88 | let ip = statement.read::("ip").unwrap(); 89 | info!("Found device with udid {} already in db", cloned_udid); 90 | 91 | // Delete the device from the database 92 | let query = "DELETE FROM devices WHERE udid = ?"; 93 | let mut statement = match crate::db::db_prepare(&db, query) { 94 | Some(s) => s, 95 | None => { 96 | log::error!("Failed to prepare query!"); 97 | return None; 98 | } 99 | }; 100 | statement 101 | .bind((1, cloned_udid.to_string().as_str())) 102 | .unwrap(); 103 | if crate::db::statement_next(&mut statement).is_none() { 104 | log::error!("Failed to enact the statement"); 105 | } 106 | 107 | Some(ip) 108 | } else { 109 | None 110 | } 111 | }) 112 | .await 113 | { 114 | Ok(ip) => ip, 115 | Err(e) => { 116 | info!("Failed to get IP from database: {:?}", e); 117 | return Err((StatusCode::INTERNAL_SERVER_ERROR, "failed to get IP")); 118 | } 119 | }; 120 | 121 | let register_mode = std::env::var("ALLOW_REGISTRATION") 122 | .unwrap_or("1".to_string()) 123 | .parse::() 124 | .unwrap(); 125 | 126 | let client_config: Vec; 127 | let ip_final: Ipv6Addr; 128 | 129 | if register_mode == 1 { 130 | // register using wireguard 131 | let wireguard_config_name = 132 | std::env::var("WIREGUARD_CONFIG_NAME").unwrap_or("jitstreamer".to_string()); 133 | let wireguard_conf = format!("/etc/wireguard/{wireguard_config_name}.conf"); 134 | let wireguard_port = std::env::var("WIREGUARD_PORT") 135 | .unwrap_or("51869".to_string()) 136 | .parse::() 137 | .unwrap_or(51869); 138 | let wireguard_server_address = 139 | std::env::var("WIREGUARD_SERVER_ADDRESS").unwrap_or("fd00::/128".to_string()); 140 | let wireguard_endpoint = 141 | std::env::var("WIREGUARD_ENDPOINT").unwrap_or("jitstreamer.jkcoxson.com".to_string()); 142 | let wireguard_server_allowed_ips = 143 | std::env::var("WIREGUARD_SERVER_ALLOWED_IPS").unwrap_or("fd00::/64".to_string()); 144 | 145 | // Read the Wireguard config file 146 | info!("Reading Wireguard server config"); 147 | let mut server_peer = match wg_config::WgConf::open(&wireguard_conf) { 148 | Ok(conf) => conf, 149 | Err(e) => { 150 | info!("Failed to open Wireguard config: {:?}", e); 151 | if let wg_config::WgConfError::NotFound(_) = e { 152 | // Generate a new one 153 | 154 | let key = 155 | wg_config::WgKey::generate_private_key().expect("failed to generate key"); 156 | let interface = wg_config::WgInterface::new( 157 | key, 158 | wireguard_server_address.parse().unwrap(), 159 | Some(wireguard_port), 160 | None, 161 | None, 162 | None, 163 | ) 164 | .unwrap(); 165 | 166 | wg_config::WgConf::create(wireguard_conf.as_str(), interface, None) 167 | .expect("failed to create config"); 168 | 169 | info!("Created new Wireguard config"); 170 | 171 | wg_config::WgConf::open(wireguard_conf.as_str()).unwrap() 172 | } else { 173 | return Err(( 174 | StatusCode::INTERNAL_SERVER_ERROR, 175 | "failed to open server Wireguard config", 176 | )); 177 | } 178 | } 179 | }; 180 | let mut public_ip = None; 181 | if let Some(ip) = ip { 182 | match server_peer.peers() { 183 | Ok(peers) => { 184 | for peer in peers { 185 | let peer_ip = peer.allowed_ips(); 186 | if ip.is_empty() { 187 | continue; 188 | } 189 | if peer_ip[0].to_string() == ip { 190 | info!("Found peer with IP {}", ip); 191 | 192 | public_ip = Some(peer.public_key().to_owned()); 193 | } 194 | } 195 | } 196 | Err(e) => { 197 | info!("Failed to get peers: {:?}", e); 198 | return Err((StatusCode::INTERNAL_SERVER_ERROR, "failed to get peers")); 199 | } 200 | } 201 | } 202 | 203 | if let Some(public_ip) = public_ip { 204 | info!("Removing existing peer"); 205 | server_peer = server_peer.remove_peer_by_pub_key(&public_ip).unwrap(); 206 | } 207 | 208 | info!("Generating IPv6 from UDID"); 209 | let ip = generate_ipv6_from_udid(udid.as_str()); 210 | ip_final = ip; 211 | 212 | // Generate a new peer for the device 213 | info!("Generating peer"); 214 | client_config = match server_peer.generate_peer( 215 | std::net::IpAddr::V6(ip), 216 | wireguard_endpoint.parse().unwrap(), 217 | vec![wireguard_server_allowed_ips.parse().unwrap()], 218 | None, 219 | true, 220 | Some(20), 221 | ) { 222 | Ok(config) => config.to_string().as_bytes().to_vec(), 223 | Err(e) => { 224 | info!("Failed to generate peer: {:?}", e); 225 | return Err((StatusCode::INTERNAL_SERVER_ERROR, "failed to generate peer")); 226 | } 227 | }; 228 | } else if register_mode == 2 { 229 | // register directly using request IP 230 | ip_final = match client_ip.0 { 231 | IpAddr::V4(v4) => v4.to_ipv6_mapped(), 232 | IpAddr::V6(v6) => v6, 233 | }; 234 | client_config = ip_final.to_string().as_bytes().to_vec(); 235 | } else { 236 | return Err(( 237 | StatusCode::INTERNAL_SERVER_ERROR, 238 | "Unknown registration mode", 239 | )); 240 | } 241 | 242 | // Save the plist to the storage 243 | let plist_storage_path = std::env::var("PLIST_STORAGE").unwrap_or( 244 | match std::env::consts::OS { 245 | "macos" => "/var/db/lockdown", 246 | "linux" => "/var/lib/lockdown", 247 | "windows" => "C:/ProgramData/Apple/Lockdown", 248 | _ => panic!("Unsupported OS, specify a path"), 249 | } 250 | .to_string(), 251 | ); 252 | 253 | // Create the folder if it doesn't exist 254 | if let Err(e) = tokio::fs::create_dir_all(&plist_storage_path).await { 255 | log::error!("Failed to create plist storage path: {e:?}"); 256 | } 257 | 258 | tokio::fs::write( 259 | format!("{plist_storage_path}/{udid}.plist"), 260 | &plist_bytes.to_vec(), 261 | ) 262 | .await 263 | .map_err(|e| { 264 | info!("Failed to save plist: {:?}", e); 265 | (StatusCode::INTERNAL_SERVER_ERROR, "failed to save plist") 266 | })?; 267 | 268 | // Save the IP to the database 269 | tokio::task::spawn_blocking(move || { 270 | let db = match sqlite::open("jitstreamer.db") { 271 | Ok(db) => db, 272 | Err(e) => { 273 | info!("Failed to open database: {:?}", e); 274 | return; 275 | } 276 | }; 277 | 278 | // Insert the device into the database 279 | let query = "INSERT INTO devices (udid, ip, last_used) VALUES (?, ?, CURRENT_TIMESTAMP)"; 280 | let mut statement = match crate::db::db_prepare(&db, query) { 281 | Some(s) => s, 282 | None => { 283 | log::error!("Failed to prepare query!"); 284 | return; 285 | } 286 | }; 287 | statement 288 | .bind(&[(1, udid.as_str()), (2, ip_final.to_string().as_str())][..]) 289 | .unwrap(); 290 | if crate::db::statement_next(&mut statement).is_none() { 291 | log::error!("Failed to enact the statement"); 292 | } 293 | }); 294 | 295 | if register_mode == 1 { 296 | refresh_wireguard(ip_final.to_string()); 297 | } 298 | 299 | Ok(client_config.into()) 300 | } 301 | 302 | const UPLOAD_HTML: &str = include_str!("../src/upload.html"); 303 | 304 | pub async fn upload() -> Result, (StatusCode, &'static str)> { 305 | Ok(Html(UPLOAD_HTML)) 306 | } 307 | 308 | fn generate_ipv6_from_udid(udid: &str) -> std::net::Ipv6Addr { 309 | // Hash the UDID using SHA-256 310 | let mut hasher = sha2::Sha256::new(); 311 | hasher.update(udid.as_bytes()); 312 | let hash = hasher.finalize(); 313 | 314 | // Use the first 64 bits of the hash for the interface ID 315 | let interface_id = u64::from_be_bytes(hash[0..8].try_into().unwrap()); 316 | 317 | // Set the first 64 bits to the `fd00::/8` range (locally assigned address) 318 | let mut segments = [0u16; 8]; 319 | segments[0] = 0xfd00; // First segment in the `fd00::/8` range 320 | (1..8).for_each(|i| { 321 | let shift = (7 - i) * 16; 322 | segments[i] = if shift < 64 { 323 | ((interface_id >> shift) & 0xFFFF) as u16 324 | } else { 325 | 0 326 | }; 327 | }); 328 | 329 | std::net::Ipv6Addr::from(segments) 330 | } 331 | 332 | fn refresh_wireguard(ip: String) { 333 | let wireguard_config_name = 334 | std::env::var("WIREGUARD_CONFIG_NAME").unwrap_or("jitstreamer".to_string()); 335 | 336 | // wg syncconf jitstreamer <(wg-quick strip jitstreamer) 337 | let output = std::process::Command::new("bash") 338 | .arg("-c") 339 | .arg(format!( 340 | "wg syncconf jitstreamer <(wg-quick strip {wireguard_config_name})" 341 | )) 342 | .output() 343 | .expect("failed to execute process"); 344 | info!("Refreshing Wireguard: {:?}", output); 345 | 346 | // ip route add fd00::b36d:f867:9391:fb0a dev jitstreamer 347 | let output = std::process::Command::new("bash") 348 | .arg("-c") 349 | .arg(format!("ip route add {ip} dev {wireguard_config_name}")) 350 | .output() 351 | .expect("failed to add IP route"); 352 | info!("Adding route: {:?}", output); 353 | } 354 | -------------------------------------------------------------------------------- /src/sql/up.sql: -------------------------------------------------------------------------------- 1 | create table devices ( 2 | ip varchar(15) primary key, 3 | udid varchar(40) not null, 4 | last_used datetime not null 5 | ); 6 | 7 | 8 | create table downloads ( 9 | code varchar(40) primary key, 10 | contents varchar(255) not null 11 | ); 12 | 13 | create table launch_queue ( 14 | udid varchar(40) not null, 15 | ip varchar(32) not null, 16 | bundle_id varchar(255) not null, 17 | status int not null, -- 0: pending, 2: error 18 | error varchar(255), 19 | ordinal integer primary key 20 | ); 21 | -------------------------------------------------------------------------------- /src/upload.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | File Upload 7 | 8 | 9 |

Please select your paring file

10 | 11 | 12 |

13 |

14 | 15 | 43 | 44 | 45 | 46 | --------------------------------------------------------------------------------