├── .envrc ├── .github └── workflows │ ├── check.yml │ └── update.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── flake.lock ├── flake.nix ├── integration-test ├── default.nix ├── driver.sh └── test.sh ├── nixos └── module.nix └── src ├── convert.rs ├── error.rs ├── key.rs ├── main.rs ├── nix.rs ├── nix └── sign.rs ├── options.rs ├── push.rs ├── registry.rs ├── server.rs └── server └── upstream.rs /.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: "Check" 2 | on: 3 | push: 4 | pull_request: 5 | workflow_dispatch: 6 | 7 | jobs: 8 | check: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@main 13 | - name: Install nix 14 | uses: cachix/install-nix-action@master 15 | with: 16 | github_access_token: '${{ secrets.GITHUB_TOKEN }}' 17 | - name: Setup cachix 18 | uses: cachix/cachix-action@master 19 | with: 20 | name: linyinfeng 21 | signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}' 22 | - name: Nix flake check 23 | run: nix flake check 24 | 25 | upload-docker-image: 26 | if: ${{ github.event_name == 'push' }} 27 | runs-on: ubuntu-latest 28 | needs: check 29 | permissions: 30 | contents: read 31 | packages: write 32 | outputs: 33 | image_tag: ${{ steps.upload.outputs.image_tag }} 34 | steps: 35 | - name: Checkout 36 | uses: actions/checkout@main 37 | - name: Install nix 38 | uses: cachix/install-nix-action@master 39 | with: 40 | github_access_token: '${{ secrets.GITHUB_TOKEN }}' 41 | - name: Setup cachix 42 | uses: cachix/cachix-action@master 43 | with: 44 | name: linyinfeng 45 | signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}' 46 | - name: Upload docker image 47 | id: upload 48 | run: | 49 | image_archive=$(nix build .#dockerImage --no-link --print-out-paths) 50 | function push_to { 51 | echo "push to '$1'" 52 | skopeo copy \ 53 | --dest-creds "${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}" \ 54 | "docker-archive:$image_archive" \ 55 | "$1" 56 | } 57 | tag=$(nix eval .#dockerImage.imageTag --raw) 58 | echo "image_tag=$tag" >> $GITHUB_OUTPUT 59 | push_to "docker://ghcr.io/linyinfeng/oranc:$tag" 60 | if [ "${{ github.ref }}" = "refs/heads/main" ]; then 61 | push_to "docker://ghcr.io/linyinfeng/oranc:latest" 62 | fi 63 | 64 | integration-test: 65 | strategy: 66 | matrix: 67 | package-for-test: ["coreutils", "nixosTests.nginx.driver"] 68 | runs-on: ubuntu-latest 69 | needs: upload-docker-image 70 | env: 71 | PACKAGE_FOR_TEST: "github:nixos/nixpkgs/nixos-unstable#${{ matrix.package-for-test }}" 72 | CACHIX_SUBSTITUTER: "https://linyinfeng.cachix.org" 73 | CACHIX_PUBLIC_KEY: "linyinfeng.cachix.org-1:sPYQXcNrnCf7Vr7T0YmjXz5dMZ7aOKG3EqLja0xr9MM=" 74 | REGISTRY: "localhost:5000" 75 | ORANC: "localhost:5001" 76 | REPOSITORY: "test-user/oranc-cache" 77 | STORE_URL: "http://localhost:5001/registry:5000/test-user/oranc-cache" 78 | services: 79 | oranc: 80 | image: ghcr.io/linyinfeng/oranc:${{ needs.upload-docker-image.outputs.image_tag }} 81 | ports: 82 | - 5001:80 83 | env: 84 | EXTRA_ARGS: --no-ssl 85 | registry: 86 | image: registry 87 | ports: 88 | - 5000:5000 89 | steps: 90 | - name: Checkout 91 | uses: actions/checkout@main 92 | - name: Install nix 93 | uses: cachix/install-nix-action@master 94 | with: 95 | github_access_token: '${{ secrets.GITHUB_TOKEN }}' 96 | - name: Registry health check 97 | run: curl -v "http://$REGISTRY" 98 | - name: Oranc server health check 99 | run: curl -v "http://$ORANC" 100 | - name: Generate key pair 101 | run: | 102 | mkdir -p /tmp/nix-key-pair 103 | nix key generate-secret --key-name "oranc-test" > /tmp/nix-key-pair/secret 104 | cat /tmp/nix-key-pair/secret | nix key convert-secret-to-public > /tmp/nix-key-pair/public 105 | 106 | echo "secret key for test: $(cat /tmp/nix-key-pair/secret)" 107 | echo "public key for test: $(cat /tmp/nix-key-pair/public)" 108 | - name: Install oranc 109 | run: | 110 | nix build .#oranc \ 111 | --extra-substituters "$CACHIX_SUBSTITUTER" \ 112 | --extra-trusted-public-keys "$CACHIX_PUBLIC_KEY" \ 113 | --out-link /tmp/oranc 114 | - name: Initialize registry 115 | run: | 116 | /tmp/oranc/bin/oranc \ 117 | push \ 118 | --no-ssl \ 119 | --registry "$REGISTRY" \ 120 | --repository "$REPOSITORY" \ 121 | initialize 122 | curl -v "$STORE_URL/nix-cache-info" 123 | - name: Get test packages from cache.nixos.org 124 | run: | 125 | nix path-info --derivation --recursive "$PACKAGE_FOR_TEST" > /tmp/derivers 126 | 127 | nix build "$PACKAGE_FOR_TEST" --no-link --print-out-paths > /tmp/derived 128 | cat /tmp/derived | xargs nix path-info --recursive > /tmp/derived_closure 129 | cat /tmp/derivers /tmp/derived_closure | sort | uniq > /tmp/store_paths 130 | 131 | echo "derivers: $(cat /tmp/derivers | wc -l)" 132 | echo "derived: $(cat /tmp/derived | wc -l)" 133 | echo "derived closure: $(cat /tmp/derived_closure | wc -l)" 134 | echo "store paths: $(cat /tmp/store_paths | wc -l)" 135 | - name: Push to cache 136 | run: | 137 | export ORANC_SIGNING_KEY="$(cat /tmp/nix-key-pair/secret)" 138 | 139 | # sign first,then push with --already-signed 140 | # oranc will check generated signature matches already exists signature 141 | cat /tmp/derived_closure | \ 142 | xargs nix store sign --key-file /tmp/nix-key-pair/secret 143 | 144 | # push everything 145 | cat /tmp/store_paths | \ 146 | sudo -E /tmp/oranc/bin/oranc \ 147 | push \ 148 | --no-ssl \ 149 | --registry "$REGISTRY" \ 150 | --repository "$REPOSITORY" \ 151 | --excluded-signing-key-pattern '^$' \ 152 | --already-signed 153 | - name: Verify remote store 154 | run: | 155 | cat /tmp/derived_closure | \ 156 | xargs nix store verify \ 157 | --store "$STORE_URL" \ 158 | --trusted-public-keys "$(cat /tmp/nix-key-pair/public)" 159 | - name: GC local store 160 | run: | 161 | nix store gc 162 | - name: Get test packages from registry 163 | run: | 164 | # instantiate derivations again 165 | nix path-info --derivation --recursive "$PACKAGE_FOR_TEST" > /dev/null 166 | cat /tmp/derived | \ 167 | xargs nix build \ 168 | --no-link \ 169 | --max-jobs 0 \ 170 | --substituters "$STORE_URL" \ 171 | --trusted-public-keys "$(cat /tmp/nix-key-pair/public)" 172 | - name: Verify local store 173 | run: | 174 | cat /tmp/derived_closure | \ 175 | xargs nix store verify \ 176 | --trusted-public-keys "$(cat /tmp/nix-key-pair/public)" 177 | -------------------------------------------------------------------------------- /.github/workflows/update.yml: -------------------------------------------------------------------------------- 1 | name: "Automated update" 2 | on: 3 | schedule: 4 | - cron: '0 16 * * 5' 5 | workflow_dispatch: 6 | push: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | update: 12 | runs-on: ubuntu-latest 13 | permissions: 14 | contents: read 15 | packages: write 16 | services: 17 | oranc: 18 | image: ghcr.io/linyinfeng/oranc 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@main 22 | with: 23 | ref: main 24 | token: '${{ secrets.PAT_FOR_AUTOMATED_UPDATE }}' 25 | - name: Install nix 26 | uses: cachix/install-nix-action@master 27 | with: 28 | github_access_token: '${{ secrets.GITHUB_TOKEN }}' 29 | - name: Setup cachix 30 | uses: cachix/cachix-action@master 31 | with: 32 | name: linyinfeng 33 | signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}' 34 | - name: Git config 35 | run: | 36 | git config --global user.email "nano@linyinfeng.com" 37 | git config --global user.name "Nano" 38 | - name: Nix flake update 39 | run: | 40 | nix flake update --commit-lock-file 41 | - name: Cargo update 42 | run: | 43 | nix develop --command cargo update 44 | if [ -z $(git status --porcelain) ]; then 45 | echo "clean, skip..." 46 | else 47 | git add --all 48 | git commit --message "Cargo update" 49 | fi 50 | - name: Nix flake check 51 | run: | 52 | nix flake check 53 | - name: Git push 54 | run: | 55 | git push 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /result* 3 | /.direnv 4 | /.vscode 5 | -------------------------------------------------------------------------------- /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 = "android-tzdata" 31 | version = "0.1.1" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 34 | 35 | [[package]] 36 | name = "android_system_properties" 37 | version = "0.1.5" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 40 | dependencies = [ 41 | "libc", 42 | ] 43 | 44 | [[package]] 45 | name = "anstream" 46 | version = "0.6.18" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 49 | dependencies = [ 50 | "anstyle", 51 | "anstyle-parse", 52 | "anstyle-query", 53 | "anstyle-wincon", 54 | "colorchoice", 55 | "is_terminal_polyfill", 56 | "utf8parse", 57 | ] 58 | 59 | [[package]] 60 | name = "anstyle" 61 | version = "1.0.10" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 64 | 65 | [[package]] 66 | name = "anstyle-parse" 67 | version = "0.2.6" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 70 | dependencies = [ 71 | "utf8parse", 72 | ] 73 | 74 | [[package]] 75 | name = "anstyle-query" 76 | version = "1.1.2" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 79 | dependencies = [ 80 | "windows-sys 0.59.0", 81 | ] 82 | 83 | [[package]] 84 | name = "anstyle-wincon" 85 | version = "3.0.8" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "6680de5231bd6ee4c6191b8a1325daa282b415391ec9d3a37bd34f2060dc73fa" 88 | dependencies = [ 89 | "anstyle", 90 | "once_cell_polyfill", 91 | "windows-sys 0.59.0", 92 | ] 93 | 94 | [[package]] 95 | name = "async-channel" 96 | version = "1.9.0" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" 99 | dependencies = [ 100 | "concurrent-queue", 101 | "event-listener 2.5.3", 102 | "futures-core", 103 | ] 104 | 105 | [[package]] 106 | name = "async-channel" 107 | version = "2.3.1" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" 110 | dependencies = [ 111 | "concurrent-queue", 112 | "event-listener-strategy", 113 | "futures-core", 114 | "pin-project-lite", 115 | ] 116 | 117 | [[package]] 118 | name = "async-executor" 119 | version = "1.13.2" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa" 122 | dependencies = [ 123 | "async-task", 124 | "concurrent-queue", 125 | "fastrand", 126 | "futures-lite", 127 | "pin-project-lite", 128 | "slab", 129 | ] 130 | 131 | [[package]] 132 | name = "async-global-executor" 133 | version = "2.4.1" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" 136 | dependencies = [ 137 | "async-channel 2.3.1", 138 | "async-executor", 139 | "async-io", 140 | "async-lock", 141 | "blocking", 142 | "futures-lite", 143 | "once_cell", 144 | ] 145 | 146 | [[package]] 147 | name = "async-io" 148 | version = "2.4.1" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "1237c0ae75a0f3765f58910ff9cdd0a12eeb39ab2f4c7de23262f337f0aacbb3" 151 | dependencies = [ 152 | "async-lock", 153 | "cfg-if", 154 | "concurrent-queue", 155 | "futures-io", 156 | "futures-lite", 157 | "parking", 158 | "polling", 159 | "rustix", 160 | "slab", 161 | "tracing", 162 | "windows-sys 0.59.0", 163 | ] 164 | 165 | [[package]] 166 | name = "async-lock" 167 | version = "3.4.0" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" 170 | dependencies = [ 171 | "event-listener 5.4.0", 172 | "event-listener-strategy", 173 | "pin-project-lite", 174 | ] 175 | 176 | [[package]] 177 | name = "async-process" 178 | version = "2.3.1" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "cde3f4e40e6021d7acffc90095cbd6dc54cb593903d1de5832f435eb274b85dc" 181 | dependencies = [ 182 | "async-channel 2.3.1", 183 | "async-io", 184 | "async-lock", 185 | "async-signal", 186 | "async-task", 187 | "blocking", 188 | "cfg-if", 189 | "event-listener 5.4.0", 190 | "futures-lite", 191 | "rustix", 192 | "tracing", 193 | ] 194 | 195 | [[package]] 196 | name = "async-signal" 197 | version = "0.2.11" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "d7605a4e50d4b06df3898d5a70bf5fde51ed9059b0434b73105193bc27acce0d" 200 | dependencies = [ 201 | "async-io", 202 | "async-lock", 203 | "atomic-waker", 204 | "cfg-if", 205 | "futures-core", 206 | "futures-io", 207 | "rustix", 208 | "signal-hook-registry", 209 | "slab", 210 | "windows-sys 0.59.0", 211 | ] 212 | 213 | [[package]] 214 | name = "async-std" 215 | version = "1.13.1" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "730294c1c08c2e0f85759590518f6333f0d5a0a766a27d519c1b244c3dfd8a24" 218 | dependencies = [ 219 | "async-channel 1.9.0", 220 | "async-global-executor", 221 | "async-io", 222 | "async-lock", 223 | "async-process", 224 | "crossbeam-utils", 225 | "futures-channel", 226 | "futures-core", 227 | "futures-io", 228 | "futures-lite", 229 | "gloo-timers", 230 | "kv-log-macro", 231 | "log", 232 | "memchr", 233 | "once_cell", 234 | "pin-project-lite", 235 | "pin-utils", 236 | "slab", 237 | "wasm-bindgen-futures", 238 | ] 239 | 240 | [[package]] 241 | name = "async-tar" 242 | version = "0.5.0" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "a42f905d4f623faf634bbd1e001e84e0efc24694afa64be9ad239bf6ca49e1f8" 245 | dependencies = [ 246 | "async-std", 247 | "filetime", 248 | "libc", 249 | "pin-project", 250 | "redox_syscall 0.2.16", 251 | "xattr", 252 | ] 253 | 254 | [[package]] 255 | name = "async-task" 256 | version = "4.7.1" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" 259 | 260 | [[package]] 261 | name = "atomic-waker" 262 | version = "1.1.2" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 265 | 266 | [[package]] 267 | name = "autocfg" 268 | version = "1.4.0" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 271 | 272 | [[package]] 273 | name = "backtrace" 274 | version = "0.3.75" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" 277 | dependencies = [ 278 | "addr2line", 279 | "cfg-if", 280 | "libc", 281 | "miniz_oxide", 282 | "object", 283 | "rustc-demangle", 284 | "windows-targets 0.52.6", 285 | ] 286 | 287 | [[package]] 288 | name = "base64" 289 | version = "0.13.1" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" 292 | 293 | [[package]] 294 | name = "base64" 295 | version = "0.21.7" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" 298 | 299 | [[package]] 300 | name = "bitflags" 301 | version = "1.3.2" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 304 | 305 | [[package]] 306 | name = "bitflags" 307 | version = "2.9.1" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" 310 | 311 | [[package]] 312 | name = "block-buffer" 313 | version = "0.10.4" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 316 | dependencies = [ 317 | "generic-array", 318 | ] 319 | 320 | [[package]] 321 | name = "blocking" 322 | version = "1.6.1" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" 325 | dependencies = [ 326 | "async-channel 2.3.1", 327 | "async-task", 328 | "futures-io", 329 | "futures-lite", 330 | "piper", 331 | ] 332 | 333 | [[package]] 334 | name = "bumpalo" 335 | version = "3.17.0" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" 338 | 339 | [[package]] 340 | name = "byteorder" 341 | version = "1.5.0" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 344 | 345 | [[package]] 346 | name = "bytes" 347 | version = "1.10.1" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 350 | 351 | [[package]] 352 | name = "camino" 353 | version = "1.1.9" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" 356 | 357 | [[package]] 358 | name = "cc" 359 | version = "1.2.25" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951" 362 | dependencies = [ 363 | "jobserver", 364 | "libc", 365 | "shlex", 366 | ] 367 | 368 | [[package]] 369 | name = "cfg-if" 370 | version = "1.0.0" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 373 | 374 | [[package]] 375 | name = "chrono" 376 | version = "0.4.41" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" 379 | dependencies = [ 380 | "android-tzdata", 381 | "iana-time-zone", 382 | "js-sys", 383 | "num-traits", 384 | "serde", 385 | "wasm-bindgen", 386 | "windows-link", 387 | ] 388 | 389 | [[package]] 390 | name = "clap" 391 | version = "4.5.39" 392 | source = "registry+https://github.com/rust-lang/crates.io-index" 393 | checksum = "fd60e63e9be68e5fb56422e397cf9baddded06dae1d2e523401542383bc72a9f" 394 | dependencies = [ 395 | "clap_builder", 396 | "clap_derive", 397 | ] 398 | 399 | [[package]] 400 | name = "clap_builder" 401 | version = "4.5.39" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "89cc6392a1f72bbeb820d71f32108f61fdaf18bc526e1d23954168a67759ef51" 404 | dependencies = [ 405 | "anstream", 406 | "anstyle", 407 | "clap_lex", 408 | "strsim", 409 | ] 410 | 411 | [[package]] 412 | name = "clap_complete" 413 | version = "4.5.52" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "1a554639e42d0c838336fc4fbedb9e2df3ad1fa4acda149f9126b4ccfcd7900f" 416 | dependencies = [ 417 | "clap", 418 | ] 419 | 420 | [[package]] 421 | name = "clap_derive" 422 | version = "4.5.32" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" 425 | dependencies = [ 426 | "heck", 427 | "proc-macro2", 428 | "quote", 429 | "syn", 430 | ] 431 | 432 | [[package]] 433 | name = "clap_lex" 434 | version = "0.7.4" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 437 | 438 | [[package]] 439 | name = "colorchoice" 440 | version = "1.0.3" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 443 | 444 | [[package]] 445 | name = "concurrent-queue" 446 | version = "2.5.0" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" 449 | dependencies = [ 450 | "crossbeam-utils", 451 | ] 452 | 453 | [[package]] 454 | name = "core-foundation" 455 | version = "0.9.4" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 458 | dependencies = [ 459 | "core-foundation-sys", 460 | "libc", 461 | ] 462 | 463 | [[package]] 464 | name = "core-foundation-sys" 465 | version = "0.8.7" 466 | source = "registry+https://github.com/rust-lang/crates.io-index" 467 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 468 | 469 | [[package]] 470 | name = "cpufeatures" 471 | version = "0.2.17" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 474 | dependencies = [ 475 | "libc", 476 | ] 477 | 478 | [[package]] 479 | name = "crossbeam-utils" 480 | version = "0.8.21" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 483 | 484 | [[package]] 485 | name = "crypto-common" 486 | version = "0.1.6" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 489 | dependencies = [ 490 | "generic-array", 491 | "typenum", 492 | ] 493 | 494 | [[package]] 495 | name = "ct-codecs" 496 | version = "1.1.6" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "9b10589d1a5e400d61f9f38f12f884cfd080ff345de8f17efda36fe0e4a02aa8" 499 | 500 | [[package]] 501 | name = "data-encoding" 502 | version = "2.9.0" 503 | source = "registry+https://github.com/rust-lang/crates.io-index" 504 | checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" 505 | 506 | [[package]] 507 | name = "digest" 508 | version = "0.10.7" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 511 | dependencies = [ 512 | "block-buffer", 513 | "crypto-common", 514 | "subtle", 515 | ] 516 | 517 | [[package]] 518 | name = "displaydoc" 519 | version = "0.2.5" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 522 | dependencies = [ 523 | "proc-macro2", 524 | "quote", 525 | "syn", 526 | ] 527 | 528 | [[package]] 529 | name = "ed25519-compact" 530 | version = "2.1.1" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "e9b3460f44bea8cd47f45a0c70892f1eff856d97cd55358b2f73f663789f6190" 533 | dependencies = [ 534 | "ct-codecs", 535 | "getrandom 0.2.16", 536 | ] 537 | 538 | [[package]] 539 | name = "encoding_rs" 540 | version = "0.8.35" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" 543 | dependencies = [ 544 | "cfg-if", 545 | ] 546 | 547 | [[package]] 548 | name = "env_logger" 549 | version = "0.10.2" 550 | source = "registry+https://github.com/rust-lang/crates.io-index" 551 | checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" 552 | dependencies = [ 553 | "humantime", 554 | "is-terminal", 555 | "log", 556 | "regex", 557 | "termcolor", 558 | ] 559 | 560 | [[package]] 561 | name = "equivalent" 562 | version = "1.0.2" 563 | source = "registry+https://github.com/rust-lang/crates.io-index" 564 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 565 | 566 | [[package]] 567 | name = "errno" 568 | version = "0.3.12" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" 571 | dependencies = [ 572 | "libc", 573 | "windows-sys 0.59.0", 574 | ] 575 | 576 | [[package]] 577 | name = "event-listener" 578 | version = "2.5.3" 579 | source = "registry+https://github.com/rust-lang/crates.io-index" 580 | checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" 581 | 582 | [[package]] 583 | name = "event-listener" 584 | version = "5.4.0" 585 | source = "registry+https://github.com/rust-lang/crates.io-index" 586 | checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" 587 | dependencies = [ 588 | "concurrent-queue", 589 | "parking", 590 | "pin-project-lite", 591 | ] 592 | 593 | [[package]] 594 | name = "event-listener-strategy" 595 | version = "0.5.4" 596 | source = "registry+https://github.com/rust-lang/crates.io-index" 597 | checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" 598 | dependencies = [ 599 | "event-listener 5.4.0", 600 | "pin-project-lite", 601 | ] 602 | 603 | [[package]] 604 | name = "fallible-iterator" 605 | version = "0.3.0" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" 608 | 609 | [[package]] 610 | name = "fallible-streaming-iterator" 611 | version = "0.1.9" 612 | source = "registry+https://github.com/rust-lang/crates.io-index" 613 | checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" 614 | 615 | [[package]] 616 | name = "fastrand" 617 | version = "2.3.0" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 620 | 621 | [[package]] 622 | name = "filetime" 623 | version = "0.2.25" 624 | source = "registry+https://github.com/rust-lang/crates.io-index" 625 | checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" 626 | dependencies = [ 627 | "cfg-if", 628 | "libc", 629 | "libredox", 630 | "windows-sys 0.59.0", 631 | ] 632 | 633 | [[package]] 634 | name = "fnv" 635 | version = "1.0.7" 636 | source = "registry+https://github.com/rust-lang/crates.io-index" 637 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 638 | 639 | [[package]] 640 | name = "foldhash" 641 | version = "0.1.5" 642 | source = "registry+https://github.com/rust-lang/crates.io-index" 643 | checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" 644 | 645 | [[package]] 646 | name = "foreign-types" 647 | version = "0.3.2" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 650 | dependencies = [ 651 | "foreign-types-shared", 652 | ] 653 | 654 | [[package]] 655 | name = "foreign-types-shared" 656 | version = "0.1.1" 657 | source = "registry+https://github.com/rust-lang/crates.io-index" 658 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 659 | 660 | [[package]] 661 | name = "form_urlencoded" 662 | version = "1.2.1" 663 | source = "registry+https://github.com/rust-lang/crates.io-index" 664 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 665 | dependencies = [ 666 | "percent-encoding", 667 | ] 668 | 669 | [[package]] 670 | name = "futures" 671 | version = "0.3.31" 672 | source = "registry+https://github.com/rust-lang/crates.io-index" 673 | checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" 674 | dependencies = [ 675 | "futures-channel", 676 | "futures-core", 677 | "futures-executor", 678 | "futures-io", 679 | "futures-sink", 680 | "futures-task", 681 | "futures-util", 682 | ] 683 | 684 | [[package]] 685 | name = "futures-channel" 686 | version = "0.3.31" 687 | source = "registry+https://github.com/rust-lang/crates.io-index" 688 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 689 | dependencies = [ 690 | "futures-core", 691 | "futures-sink", 692 | ] 693 | 694 | [[package]] 695 | name = "futures-core" 696 | version = "0.3.31" 697 | source = "registry+https://github.com/rust-lang/crates.io-index" 698 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 699 | 700 | [[package]] 701 | name = "futures-executor" 702 | version = "0.3.31" 703 | source = "registry+https://github.com/rust-lang/crates.io-index" 704 | checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" 705 | dependencies = [ 706 | "futures-core", 707 | "futures-task", 708 | "futures-util", 709 | ] 710 | 711 | [[package]] 712 | name = "futures-io" 713 | version = "0.3.31" 714 | source = "registry+https://github.com/rust-lang/crates.io-index" 715 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 716 | 717 | [[package]] 718 | name = "futures-lite" 719 | version = "2.6.0" 720 | source = "registry+https://github.com/rust-lang/crates.io-index" 721 | checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" 722 | dependencies = [ 723 | "fastrand", 724 | "futures-core", 725 | "futures-io", 726 | "parking", 727 | "pin-project-lite", 728 | ] 729 | 730 | [[package]] 731 | name = "futures-macro" 732 | version = "0.3.31" 733 | source = "registry+https://github.com/rust-lang/crates.io-index" 734 | checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 735 | dependencies = [ 736 | "proc-macro2", 737 | "quote", 738 | "syn", 739 | ] 740 | 741 | [[package]] 742 | name = "futures-sink" 743 | version = "0.3.31" 744 | source = "registry+https://github.com/rust-lang/crates.io-index" 745 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 746 | 747 | [[package]] 748 | name = "futures-task" 749 | version = "0.3.31" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 752 | 753 | [[package]] 754 | name = "futures-util" 755 | version = "0.3.31" 756 | source = "registry+https://github.com/rust-lang/crates.io-index" 757 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 758 | dependencies = [ 759 | "futures-channel", 760 | "futures-core", 761 | "futures-io", 762 | "futures-macro", 763 | "futures-sink", 764 | "futures-task", 765 | "memchr", 766 | "pin-project-lite", 767 | "pin-utils", 768 | "slab", 769 | ] 770 | 771 | [[package]] 772 | name = "generic-array" 773 | version = "0.14.7" 774 | source = "registry+https://github.com/rust-lang/crates.io-index" 775 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 776 | dependencies = [ 777 | "typenum", 778 | "version_check", 779 | ] 780 | 781 | [[package]] 782 | name = "getrandom" 783 | version = "0.2.16" 784 | source = "registry+https://github.com/rust-lang/crates.io-index" 785 | checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" 786 | dependencies = [ 787 | "cfg-if", 788 | "js-sys", 789 | "libc", 790 | "wasi 0.11.0+wasi-snapshot-preview1", 791 | "wasm-bindgen", 792 | ] 793 | 794 | [[package]] 795 | name = "getrandom" 796 | version = "0.3.3" 797 | source = "registry+https://github.com/rust-lang/crates.io-index" 798 | checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" 799 | dependencies = [ 800 | "cfg-if", 801 | "libc", 802 | "r-efi", 803 | "wasi 0.14.2+wasi-0.2.4", 804 | ] 805 | 806 | [[package]] 807 | name = "gimli" 808 | version = "0.31.1" 809 | source = "registry+https://github.com/rust-lang/crates.io-index" 810 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 811 | 812 | [[package]] 813 | name = "gloo-timers" 814 | version = "0.3.0" 815 | source = "registry+https://github.com/rust-lang/crates.io-index" 816 | checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" 817 | dependencies = [ 818 | "futures-channel", 819 | "futures-core", 820 | "js-sys", 821 | "wasm-bindgen", 822 | ] 823 | 824 | [[package]] 825 | name = "h2" 826 | version = "0.3.26" 827 | source = "registry+https://github.com/rust-lang/crates.io-index" 828 | checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" 829 | dependencies = [ 830 | "bytes", 831 | "fnv", 832 | "futures-core", 833 | "futures-sink", 834 | "futures-util", 835 | "http 0.2.12", 836 | "indexmap", 837 | "slab", 838 | "tokio", 839 | "tokio-util", 840 | "tracing", 841 | ] 842 | 843 | [[package]] 844 | name = "hashbrown" 845 | version = "0.15.3" 846 | source = "registry+https://github.com/rust-lang/crates.io-index" 847 | checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" 848 | dependencies = [ 849 | "foldhash", 850 | ] 851 | 852 | [[package]] 853 | name = "hashlink" 854 | version = "0.10.0" 855 | source = "registry+https://github.com/rust-lang/crates.io-index" 856 | checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" 857 | dependencies = [ 858 | "hashbrown", 859 | ] 860 | 861 | [[package]] 862 | name = "headers" 863 | version = "0.3.9" 864 | source = "registry+https://github.com/rust-lang/crates.io-index" 865 | checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" 866 | dependencies = [ 867 | "base64 0.21.7", 868 | "bytes", 869 | "headers-core", 870 | "http 0.2.12", 871 | "httpdate", 872 | "mime", 873 | "sha1", 874 | ] 875 | 876 | [[package]] 877 | name = "headers-core" 878 | version = "0.2.0" 879 | source = "registry+https://github.com/rust-lang/crates.io-index" 880 | checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" 881 | dependencies = [ 882 | "http 0.2.12", 883 | ] 884 | 885 | [[package]] 886 | name = "heck" 887 | version = "0.5.0" 888 | source = "registry+https://github.com/rust-lang/crates.io-index" 889 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 890 | 891 | [[package]] 892 | name = "hermit-abi" 893 | version = "0.5.1" 894 | source = "registry+https://github.com/rust-lang/crates.io-index" 895 | checksum = "f154ce46856750ed433c8649605bf7ed2de3bc35fd9d2a9f30cddd873c80cb08" 896 | 897 | [[package]] 898 | name = "hmac" 899 | version = "0.12.1" 900 | source = "registry+https://github.com/rust-lang/crates.io-index" 901 | checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 902 | dependencies = [ 903 | "digest", 904 | ] 905 | 906 | [[package]] 907 | name = "http" 908 | version = "0.2.12" 909 | source = "registry+https://github.com/rust-lang/crates.io-index" 910 | checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" 911 | dependencies = [ 912 | "bytes", 913 | "fnv", 914 | "itoa", 915 | ] 916 | 917 | [[package]] 918 | name = "http" 919 | version = "1.3.1" 920 | source = "registry+https://github.com/rust-lang/crates.io-index" 921 | checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" 922 | dependencies = [ 923 | "bytes", 924 | "fnv", 925 | "itoa", 926 | ] 927 | 928 | [[package]] 929 | name = "http-auth" 930 | version = "0.1.10" 931 | source = "registry+https://github.com/rust-lang/crates.io-index" 932 | checksum = "150fa4a9462ef926824cf4519c84ed652ca8f4fbae34cb8af045b5cbcaf98822" 933 | dependencies = [ 934 | "memchr", 935 | ] 936 | 937 | [[package]] 938 | name = "http-body" 939 | version = "0.4.6" 940 | source = "registry+https://github.com/rust-lang/crates.io-index" 941 | checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" 942 | dependencies = [ 943 | "bytes", 944 | "http 0.2.12", 945 | "pin-project-lite", 946 | ] 947 | 948 | [[package]] 949 | name = "httparse" 950 | version = "1.10.1" 951 | source = "registry+https://github.com/rust-lang/crates.io-index" 952 | checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" 953 | 954 | [[package]] 955 | name = "httpdate" 956 | version = "1.0.3" 957 | source = "registry+https://github.com/rust-lang/crates.io-index" 958 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 959 | 960 | [[package]] 961 | name = "humantime" 962 | version = "2.2.0" 963 | source = "registry+https://github.com/rust-lang/crates.io-index" 964 | checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" 965 | 966 | [[package]] 967 | name = "hyper" 968 | version = "0.14.32" 969 | source = "registry+https://github.com/rust-lang/crates.io-index" 970 | checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" 971 | dependencies = [ 972 | "bytes", 973 | "futures-channel", 974 | "futures-core", 975 | "futures-util", 976 | "h2", 977 | "http 0.2.12", 978 | "http-body", 979 | "httparse", 980 | "httpdate", 981 | "itoa", 982 | "pin-project-lite", 983 | "socket2", 984 | "tokio", 985 | "tower-service", 986 | "tracing", 987 | "want", 988 | ] 989 | 990 | [[package]] 991 | name = "hyper-tls" 992 | version = "0.5.0" 993 | source = "registry+https://github.com/rust-lang/crates.io-index" 994 | checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" 995 | dependencies = [ 996 | "bytes", 997 | "hyper", 998 | "native-tls", 999 | "tokio", 1000 | "tokio-native-tls", 1001 | ] 1002 | 1003 | [[package]] 1004 | name = "iana-time-zone" 1005 | version = "0.1.63" 1006 | source = "registry+https://github.com/rust-lang/crates.io-index" 1007 | checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" 1008 | dependencies = [ 1009 | "android_system_properties", 1010 | "core-foundation-sys", 1011 | "iana-time-zone-haiku", 1012 | "js-sys", 1013 | "log", 1014 | "wasm-bindgen", 1015 | "windows-core", 1016 | ] 1017 | 1018 | [[package]] 1019 | name = "iana-time-zone-haiku" 1020 | version = "0.1.2" 1021 | source = "registry+https://github.com/rust-lang/crates.io-index" 1022 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 1023 | dependencies = [ 1024 | "cc", 1025 | ] 1026 | 1027 | [[package]] 1028 | name = "icu_collections" 1029 | version = "2.0.0" 1030 | source = "registry+https://github.com/rust-lang/crates.io-index" 1031 | checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" 1032 | dependencies = [ 1033 | "displaydoc", 1034 | "potential_utf", 1035 | "yoke", 1036 | "zerofrom", 1037 | "zerovec", 1038 | ] 1039 | 1040 | [[package]] 1041 | name = "icu_locale_core" 1042 | version = "2.0.0" 1043 | source = "registry+https://github.com/rust-lang/crates.io-index" 1044 | checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" 1045 | dependencies = [ 1046 | "displaydoc", 1047 | "litemap", 1048 | "tinystr", 1049 | "writeable", 1050 | "zerovec", 1051 | ] 1052 | 1053 | [[package]] 1054 | name = "icu_normalizer" 1055 | version = "2.0.0" 1056 | source = "registry+https://github.com/rust-lang/crates.io-index" 1057 | checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" 1058 | dependencies = [ 1059 | "displaydoc", 1060 | "icu_collections", 1061 | "icu_normalizer_data", 1062 | "icu_properties", 1063 | "icu_provider", 1064 | "smallvec", 1065 | "zerovec", 1066 | ] 1067 | 1068 | [[package]] 1069 | name = "icu_normalizer_data" 1070 | version = "2.0.0" 1071 | source = "registry+https://github.com/rust-lang/crates.io-index" 1072 | checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" 1073 | 1074 | [[package]] 1075 | name = "icu_properties" 1076 | version = "2.0.1" 1077 | source = "registry+https://github.com/rust-lang/crates.io-index" 1078 | checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" 1079 | dependencies = [ 1080 | "displaydoc", 1081 | "icu_collections", 1082 | "icu_locale_core", 1083 | "icu_properties_data", 1084 | "icu_provider", 1085 | "potential_utf", 1086 | "zerotrie", 1087 | "zerovec", 1088 | ] 1089 | 1090 | [[package]] 1091 | name = "icu_properties_data" 1092 | version = "2.0.1" 1093 | source = "registry+https://github.com/rust-lang/crates.io-index" 1094 | checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" 1095 | 1096 | [[package]] 1097 | name = "icu_provider" 1098 | version = "2.0.0" 1099 | source = "registry+https://github.com/rust-lang/crates.io-index" 1100 | checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" 1101 | dependencies = [ 1102 | "displaydoc", 1103 | "icu_locale_core", 1104 | "stable_deref_trait", 1105 | "tinystr", 1106 | "writeable", 1107 | "yoke", 1108 | "zerofrom", 1109 | "zerotrie", 1110 | "zerovec", 1111 | ] 1112 | 1113 | [[package]] 1114 | name = "idna" 1115 | version = "1.0.3" 1116 | source = "registry+https://github.com/rust-lang/crates.io-index" 1117 | checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" 1118 | dependencies = [ 1119 | "idna_adapter", 1120 | "smallvec", 1121 | "utf8_iter", 1122 | ] 1123 | 1124 | [[package]] 1125 | name = "idna_adapter" 1126 | version = "1.2.1" 1127 | source = "registry+https://github.com/rust-lang/crates.io-index" 1128 | checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" 1129 | dependencies = [ 1130 | "icu_normalizer", 1131 | "icu_properties", 1132 | ] 1133 | 1134 | [[package]] 1135 | name = "indexmap" 1136 | version = "2.9.0" 1137 | source = "registry+https://github.com/rust-lang/crates.io-index" 1138 | checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" 1139 | dependencies = [ 1140 | "equivalent", 1141 | "hashbrown", 1142 | ] 1143 | 1144 | [[package]] 1145 | name = "ipnet" 1146 | version = "2.11.0" 1147 | source = "registry+https://github.com/rust-lang/crates.io-index" 1148 | checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" 1149 | 1150 | [[package]] 1151 | name = "is-terminal" 1152 | version = "0.4.16" 1153 | source = "registry+https://github.com/rust-lang/crates.io-index" 1154 | checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" 1155 | dependencies = [ 1156 | "hermit-abi", 1157 | "libc", 1158 | "windows-sys 0.59.0", 1159 | ] 1160 | 1161 | [[package]] 1162 | name = "is_executable" 1163 | version = "1.0.4" 1164 | source = "registry+https://github.com/rust-lang/crates.io-index" 1165 | checksum = "d4a1b5bad6f9072935961dfbf1cced2f3d129963d091b6f69f007fe04e758ae2" 1166 | dependencies = [ 1167 | "winapi", 1168 | ] 1169 | 1170 | [[package]] 1171 | name = "is_terminal_polyfill" 1172 | version = "1.70.1" 1173 | source = "registry+https://github.com/rust-lang/crates.io-index" 1174 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 1175 | 1176 | [[package]] 1177 | name = "itoa" 1178 | version = "1.0.15" 1179 | source = "registry+https://github.com/rust-lang/crates.io-index" 1180 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 1181 | 1182 | [[package]] 1183 | name = "jobserver" 1184 | version = "0.1.33" 1185 | source = "registry+https://github.com/rust-lang/crates.io-index" 1186 | checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" 1187 | dependencies = [ 1188 | "getrandom 0.3.3", 1189 | "libc", 1190 | ] 1191 | 1192 | [[package]] 1193 | name = "js-sys" 1194 | version = "0.3.77" 1195 | source = "registry+https://github.com/rust-lang/crates.io-index" 1196 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 1197 | dependencies = [ 1198 | "once_cell", 1199 | "wasm-bindgen", 1200 | ] 1201 | 1202 | [[package]] 1203 | name = "jwt" 1204 | version = "0.16.0" 1205 | source = "registry+https://github.com/rust-lang/crates.io-index" 1206 | checksum = "6204285f77fe7d9784db3fdc449ecce1a0114927a51d5a41c4c7a292011c015f" 1207 | dependencies = [ 1208 | "base64 0.13.1", 1209 | "crypto-common", 1210 | "digest", 1211 | "hmac", 1212 | "serde", 1213 | "serde_json", 1214 | "sha2", 1215 | ] 1216 | 1217 | [[package]] 1218 | name = "kv-log-macro" 1219 | version = "1.0.7" 1220 | source = "registry+https://github.com/rust-lang/crates.io-index" 1221 | checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" 1222 | dependencies = [ 1223 | "log", 1224 | ] 1225 | 1226 | [[package]] 1227 | name = "lazy_static" 1228 | version = "1.5.0" 1229 | source = "registry+https://github.com/rust-lang/crates.io-index" 1230 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 1231 | 1232 | [[package]] 1233 | name = "libc" 1234 | version = "0.2.172" 1235 | source = "registry+https://github.com/rust-lang/crates.io-index" 1236 | checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" 1237 | 1238 | [[package]] 1239 | name = "libredox" 1240 | version = "0.1.3" 1241 | source = "registry+https://github.com/rust-lang/crates.io-index" 1242 | checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" 1243 | dependencies = [ 1244 | "bitflags 2.9.1", 1245 | "libc", 1246 | "redox_syscall 0.5.12", 1247 | ] 1248 | 1249 | [[package]] 1250 | name = "libsqlite3-sys" 1251 | version = "0.34.0" 1252 | source = "registry+https://github.com/rust-lang/crates.io-index" 1253 | checksum = "91632f3b4fb6bd1d72aa3d78f41ffecfcf2b1a6648d8c241dbe7dbfaf4875e15" 1254 | dependencies = [ 1255 | "pkg-config", 1256 | "vcpkg", 1257 | ] 1258 | 1259 | [[package]] 1260 | name = "linux-raw-sys" 1261 | version = "0.9.4" 1262 | source = "registry+https://github.com/rust-lang/crates.io-index" 1263 | checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" 1264 | 1265 | [[package]] 1266 | name = "litemap" 1267 | version = "0.8.0" 1268 | source = "registry+https://github.com/rust-lang/crates.io-index" 1269 | checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" 1270 | 1271 | [[package]] 1272 | name = "log" 1273 | version = "0.4.27" 1274 | source = "registry+https://github.com/rust-lang/crates.io-index" 1275 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 1276 | dependencies = [ 1277 | "value-bag", 1278 | ] 1279 | 1280 | [[package]] 1281 | name = "maplit" 1282 | version = "1.0.2" 1283 | source = "registry+https://github.com/rust-lang/crates.io-index" 1284 | checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" 1285 | 1286 | [[package]] 1287 | name = "memchr" 1288 | version = "2.7.4" 1289 | source = "registry+https://github.com/rust-lang/crates.io-index" 1290 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 1291 | 1292 | [[package]] 1293 | name = "mime" 1294 | version = "0.3.17" 1295 | source = "registry+https://github.com/rust-lang/crates.io-index" 1296 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 1297 | 1298 | [[package]] 1299 | name = "mime_guess" 1300 | version = "2.0.5" 1301 | source = "registry+https://github.com/rust-lang/crates.io-index" 1302 | checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" 1303 | dependencies = [ 1304 | "mime", 1305 | "unicase", 1306 | ] 1307 | 1308 | [[package]] 1309 | name = "miniz_oxide" 1310 | version = "0.8.8" 1311 | source = "registry+https://github.com/rust-lang/crates.io-index" 1312 | checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" 1313 | dependencies = [ 1314 | "adler2", 1315 | ] 1316 | 1317 | [[package]] 1318 | name = "mio" 1319 | version = "1.0.4" 1320 | source = "registry+https://github.com/rust-lang/crates.io-index" 1321 | checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" 1322 | dependencies = [ 1323 | "libc", 1324 | "wasi 0.11.0+wasi-snapshot-preview1", 1325 | "windows-sys 0.59.0", 1326 | ] 1327 | 1328 | [[package]] 1329 | name = "multer" 1330 | version = "2.1.0" 1331 | source = "registry+https://github.com/rust-lang/crates.io-index" 1332 | checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" 1333 | dependencies = [ 1334 | "bytes", 1335 | "encoding_rs", 1336 | "futures-util", 1337 | "http 0.2.12", 1338 | "httparse", 1339 | "log", 1340 | "memchr", 1341 | "mime", 1342 | "spin", 1343 | "version_check", 1344 | ] 1345 | 1346 | [[package]] 1347 | name = "native-tls" 1348 | version = "0.2.14" 1349 | source = "registry+https://github.com/rust-lang/crates.io-index" 1350 | checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" 1351 | dependencies = [ 1352 | "libc", 1353 | "log", 1354 | "openssl", 1355 | "openssl-probe", 1356 | "openssl-sys", 1357 | "schannel", 1358 | "security-framework", 1359 | "security-framework-sys", 1360 | "tempfile", 1361 | ] 1362 | 1363 | [[package]] 1364 | name = "nix-base32" 1365 | version = "0.2.0" 1366 | source = "registry+https://github.com/rust-lang/crates.io-index" 1367 | checksum = "d2628953ed836273ee4262e3708a8ef63ca38bd8a922070626eef7f9e5d8d536" 1368 | 1369 | [[package]] 1370 | name = "nix-nar" 1371 | version = "0.3.0" 1372 | source = "registry+https://github.com/rust-lang/crates.io-index" 1373 | checksum = "d5549158a8b179c4fcd06a19f4bcc557db60c9cbd6771add9563f46c8d0325b5" 1374 | dependencies = [ 1375 | "camino", 1376 | "is_executable", 1377 | "symlink", 1378 | "thiserror 1.0.69", 1379 | ] 1380 | 1381 | [[package]] 1382 | name = "num-traits" 1383 | version = "0.2.19" 1384 | source = "registry+https://github.com/rust-lang/crates.io-index" 1385 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 1386 | dependencies = [ 1387 | "autocfg", 1388 | ] 1389 | 1390 | [[package]] 1391 | name = "object" 1392 | version = "0.36.7" 1393 | source = "registry+https://github.com/rust-lang/crates.io-index" 1394 | checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" 1395 | dependencies = [ 1396 | "memchr", 1397 | ] 1398 | 1399 | [[package]] 1400 | name = "oci-distribution" 1401 | version = "0.10.0" 1402 | source = "registry+https://github.com/rust-lang/crates.io-index" 1403 | checksum = "2a635cabf7a6eb4e5f13e9e82bd9503b7c2461bf277132e38638a935ebd684b4" 1404 | dependencies = [ 1405 | "bytes", 1406 | "chrono", 1407 | "futures-util", 1408 | "http 0.2.12", 1409 | "http-auth", 1410 | "jwt", 1411 | "lazy_static", 1412 | "olpc-cjson", 1413 | "regex", 1414 | "reqwest", 1415 | "serde", 1416 | "serde_json", 1417 | "sha2", 1418 | "thiserror 1.0.69", 1419 | "tokio", 1420 | "tracing", 1421 | "unicase", 1422 | ] 1423 | 1424 | [[package]] 1425 | name = "olpc-cjson" 1426 | version = "0.1.4" 1427 | source = "registry+https://github.com/rust-lang/crates.io-index" 1428 | checksum = "696183c9b5fe81a7715d074fd632e8bd46f4ccc0231a3ed7fc580a80de5f7083" 1429 | dependencies = [ 1430 | "serde", 1431 | "serde_json", 1432 | "unicode-normalization", 1433 | ] 1434 | 1435 | [[package]] 1436 | name = "once_cell" 1437 | version = "1.21.3" 1438 | source = "registry+https://github.com/rust-lang/crates.io-index" 1439 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 1440 | 1441 | [[package]] 1442 | name = "once_cell_polyfill" 1443 | version = "1.70.1" 1444 | source = "registry+https://github.com/rust-lang/crates.io-index" 1445 | checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" 1446 | 1447 | [[package]] 1448 | name = "openssl" 1449 | version = "0.10.73" 1450 | source = "registry+https://github.com/rust-lang/crates.io-index" 1451 | checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" 1452 | dependencies = [ 1453 | "bitflags 2.9.1", 1454 | "cfg-if", 1455 | "foreign-types", 1456 | "libc", 1457 | "once_cell", 1458 | "openssl-macros", 1459 | "openssl-sys", 1460 | ] 1461 | 1462 | [[package]] 1463 | name = "openssl-macros" 1464 | version = "0.1.1" 1465 | source = "registry+https://github.com/rust-lang/crates.io-index" 1466 | checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 1467 | dependencies = [ 1468 | "proc-macro2", 1469 | "quote", 1470 | "syn", 1471 | ] 1472 | 1473 | [[package]] 1474 | name = "openssl-probe" 1475 | version = "0.1.6" 1476 | source = "registry+https://github.com/rust-lang/crates.io-index" 1477 | checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" 1478 | 1479 | [[package]] 1480 | name = "openssl-sys" 1481 | version = "0.9.109" 1482 | source = "registry+https://github.com/rust-lang/crates.io-index" 1483 | checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" 1484 | dependencies = [ 1485 | "cc", 1486 | "libc", 1487 | "pkg-config", 1488 | "vcpkg", 1489 | ] 1490 | 1491 | [[package]] 1492 | name = "oranc" 1493 | version = "0.1.0" 1494 | dependencies = [ 1495 | "async-tar", 1496 | "bytes", 1497 | "clap", 1498 | "clap_complete", 1499 | "data-encoding", 1500 | "ed25519-compact", 1501 | "futures", 1502 | "http 0.2.12", 1503 | "hyper", 1504 | "log", 1505 | "maplit", 1506 | "nix-base32", 1507 | "nix-nar", 1508 | "oci-distribution", 1509 | "once_cell", 1510 | "pretty_env_logger", 1511 | "regex", 1512 | "reqwest", 1513 | "rusqlite", 1514 | "sha2", 1515 | "tempfile", 1516 | "thiserror 2.0.12", 1517 | "tokio", 1518 | "tokio-util", 1519 | "urlencoding", 1520 | "warp", 1521 | "zstd", 1522 | ] 1523 | 1524 | [[package]] 1525 | name = "parking" 1526 | version = "2.2.1" 1527 | source = "registry+https://github.com/rust-lang/crates.io-index" 1528 | checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" 1529 | 1530 | [[package]] 1531 | name = "percent-encoding" 1532 | version = "2.3.1" 1533 | source = "registry+https://github.com/rust-lang/crates.io-index" 1534 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 1535 | 1536 | [[package]] 1537 | name = "pin-project" 1538 | version = "1.1.10" 1539 | source = "registry+https://github.com/rust-lang/crates.io-index" 1540 | checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" 1541 | dependencies = [ 1542 | "pin-project-internal", 1543 | ] 1544 | 1545 | [[package]] 1546 | name = "pin-project-internal" 1547 | version = "1.1.10" 1548 | source = "registry+https://github.com/rust-lang/crates.io-index" 1549 | checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" 1550 | dependencies = [ 1551 | "proc-macro2", 1552 | "quote", 1553 | "syn", 1554 | ] 1555 | 1556 | [[package]] 1557 | name = "pin-project-lite" 1558 | version = "0.2.16" 1559 | source = "registry+https://github.com/rust-lang/crates.io-index" 1560 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 1561 | 1562 | [[package]] 1563 | name = "pin-utils" 1564 | version = "0.1.0" 1565 | source = "registry+https://github.com/rust-lang/crates.io-index" 1566 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1567 | 1568 | [[package]] 1569 | name = "piper" 1570 | version = "0.2.4" 1571 | source = "registry+https://github.com/rust-lang/crates.io-index" 1572 | checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" 1573 | dependencies = [ 1574 | "atomic-waker", 1575 | "fastrand", 1576 | "futures-io", 1577 | ] 1578 | 1579 | [[package]] 1580 | name = "pkg-config" 1581 | version = "0.3.32" 1582 | source = "registry+https://github.com/rust-lang/crates.io-index" 1583 | checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 1584 | 1585 | [[package]] 1586 | name = "polling" 1587 | version = "3.8.0" 1588 | source = "registry+https://github.com/rust-lang/crates.io-index" 1589 | checksum = "b53a684391ad002dd6a596ceb6c74fd004fdce75f4be2e3f615068abbea5fd50" 1590 | dependencies = [ 1591 | "cfg-if", 1592 | "concurrent-queue", 1593 | "hermit-abi", 1594 | "pin-project-lite", 1595 | "rustix", 1596 | "tracing", 1597 | "windows-sys 0.59.0", 1598 | ] 1599 | 1600 | [[package]] 1601 | name = "potential_utf" 1602 | version = "0.1.2" 1603 | source = "registry+https://github.com/rust-lang/crates.io-index" 1604 | checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" 1605 | dependencies = [ 1606 | "zerovec", 1607 | ] 1608 | 1609 | [[package]] 1610 | name = "ppv-lite86" 1611 | version = "0.2.21" 1612 | source = "registry+https://github.com/rust-lang/crates.io-index" 1613 | checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" 1614 | dependencies = [ 1615 | "zerocopy", 1616 | ] 1617 | 1618 | [[package]] 1619 | name = "pretty_env_logger" 1620 | version = "0.5.0" 1621 | source = "registry+https://github.com/rust-lang/crates.io-index" 1622 | checksum = "865724d4dbe39d9f3dd3b52b88d859d66bcb2d6a0acfd5ea68a65fb66d4bdc1c" 1623 | dependencies = [ 1624 | "env_logger", 1625 | "log", 1626 | ] 1627 | 1628 | [[package]] 1629 | name = "proc-macro2" 1630 | version = "1.0.95" 1631 | source = "registry+https://github.com/rust-lang/crates.io-index" 1632 | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 1633 | dependencies = [ 1634 | "unicode-ident", 1635 | ] 1636 | 1637 | [[package]] 1638 | name = "quote" 1639 | version = "1.0.40" 1640 | source = "registry+https://github.com/rust-lang/crates.io-index" 1641 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 1642 | dependencies = [ 1643 | "proc-macro2", 1644 | ] 1645 | 1646 | [[package]] 1647 | name = "r-efi" 1648 | version = "5.2.0" 1649 | source = "registry+https://github.com/rust-lang/crates.io-index" 1650 | checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" 1651 | 1652 | [[package]] 1653 | name = "rand" 1654 | version = "0.8.5" 1655 | source = "registry+https://github.com/rust-lang/crates.io-index" 1656 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1657 | dependencies = [ 1658 | "libc", 1659 | "rand_chacha", 1660 | "rand_core", 1661 | ] 1662 | 1663 | [[package]] 1664 | name = "rand_chacha" 1665 | version = "0.3.1" 1666 | source = "registry+https://github.com/rust-lang/crates.io-index" 1667 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1668 | dependencies = [ 1669 | "ppv-lite86", 1670 | "rand_core", 1671 | ] 1672 | 1673 | [[package]] 1674 | name = "rand_core" 1675 | version = "0.6.4" 1676 | source = "registry+https://github.com/rust-lang/crates.io-index" 1677 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1678 | dependencies = [ 1679 | "getrandom 0.2.16", 1680 | ] 1681 | 1682 | [[package]] 1683 | name = "redox_syscall" 1684 | version = "0.2.16" 1685 | source = "registry+https://github.com/rust-lang/crates.io-index" 1686 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 1687 | dependencies = [ 1688 | "bitflags 1.3.2", 1689 | ] 1690 | 1691 | [[package]] 1692 | name = "redox_syscall" 1693 | version = "0.5.12" 1694 | source = "registry+https://github.com/rust-lang/crates.io-index" 1695 | checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" 1696 | dependencies = [ 1697 | "bitflags 2.9.1", 1698 | ] 1699 | 1700 | [[package]] 1701 | name = "regex" 1702 | version = "1.11.1" 1703 | source = "registry+https://github.com/rust-lang/crates.io-index" 1704 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 1705 | dependencies = [ 1706 | "aho-corasick", 1707 | "memchr", 1708 | "regex-automata", 1709 | "regex-syntax", 1710 | ] 1711 | 1712 | [[package]] 1713 | name = "regex-automata" 1714 | version = "0.4.9" 1715 | source = "registry+https://github.com/rust-lang/crates.io-index" 1716 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 1717 | dependencies = [ 1718 | "aho-corasick", 1719 | "memchr", 1720 | "regex-syntax", 1721 | ] 1722 | 1723 | [[package]] 1724 | name = "regex-syntax" 1725 | version = "0.8.5" 1726 | source = "registry+https://github.com/rust-lang/crates.io-index" 1727 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 1728 | 1729 | [[package]] 1730 | name = "reqwest" 1731 | version = "0.11.27" 1732 | source = "registry+https://github.com/rust-lang/crates.io-index" 1733 | checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" 1734 | dependencies = [ 1735 | "base64 0.21.7", 1736 | "bytes", 1737 | "encoding_rs", 1738 | "futures-core", 1739 | "futures-util", 1740 | "h2", 1741 | "http 0.2.12", 1742 | "http-body", 1743 | "hyper", 1744 | "hyper-tls", 1745 | "ipnet", 1746 | "js-sys", 1747 | "log", 1748 | "mime", 1749 | "native-tls", 1750 | "once_cell", 1751 | "percent-encoding", 1752 | "pin-project-lite", 1753 | "rustls-pemfile", 1754 | "serde", 1755 | "serde_json", 1756 | "serde_urlencoded", 1757 | "sync_wrapper", 1758 | "system-configuration", 1759 | "tokio", 1760 | "tokio-native-tls", 1761 | "tokio-util", 1762 | "tower-service", 1763 | "url", 1764 | "wasm-bindgen", 1765 | "wasm-bindgen-futures", 1766 | "wasm-streams", 1767 | "web-sys", 1768 | "winreg", 1769 | ] 1770 | 1771 | [[package]] 1772 | name = "rusqlite" 1773 | version = "0.36.0" 1774 | source = "registry+https://github.com/rust-lang/crates.io-index" 1775 | checksum = "3de23c3319433716cf134eed225fe9986bc24f63bed9be9f20c329029e672dc7" 1776 | dependencies = [ 1777 | "bitflags 2.9.1", 1778 | "fallible-iterator", 1779 | "fallible-streaming-iterator", 1780 | "hashlink", 1781 | "libsqlite3-sys", 1782 | "smallvec", 1783 | ] 1784 | 1785 | [[package]] 1786 | name = "rustc-demangle" 1787 | version = "0.1.24" 1788 | source = "registry+https://github.com/rust-lang/crates.io-index" 1789 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 1790 | 1791 | [[package]] 1792 | name = "rustix" 1793 | version = "1.0.7" 1794 | source = "registry+https://github.com/rust-lang/crates.io-index" 1795 | checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" 1796 | dependencies = [ 1797 | "bitflags 2.9.1", 1798 | "errno", 1799 | "libc", 1800 | "linux-raw-sys", 1801 | "windows-sys 0.59.0", 1802 | ] 1803 | 1804 | [[package]] 1805 | name = "rustls-pemfile" 1806 | version = "1.0.4" 1807 | source = "registry+https://github.com/rust-lang/crates.io-index" 1808 | checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" 1809 | dependencies = [ 1810 | "base64 0.21.7", 1811 | ] 1812 | 1813 | [[package]] 1814 | name = "rustversion" 1815 | version = "1.0.21" 1816 | source = "registry+https://github.com/rust-lang/crates.io-index" 1817 | checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" 1818 | 1819 | [[package]] 1820 | name = "ryu" 1821 | version = "1.0.20" 1822 | source = "registry+https://github.com/rust-lang/crates.io-index" 1823 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 1824 | 1825 | [[package]] 1826 | name = "schannel" 1827 | version = "0.1.27" 1828 | source = "registry+https://github.com/rust-lang/crates.io-index" 1829 | checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" 1830 | dependencies = [ 1831 | "windows-sys 0.59.0", 1832 | ] 1833 | 1834 | [[package]] 1835 | name = "scoped-tls" 1836 | version = "1.0.1" 1837 | source = "registry+https://github.com/rust-lang/crates.io-index" 1838 | checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" 1839 | 1840 | [[package]] 1841 | name = "security-framework" 1842 | version = "2.11.1" 1843 | source = "registry+https://github.com/rust-lang/crates.io-index" 1844 | checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" 1845 | dependencies = [ 1846 | "bitflags 2.9.1", 1847 | "core-foundation", 1848 | "core-foundation-sys", 1849 | "libc", 1850 | "security-framework-sys", 1851 | ] 1852 | 1853 | [[package]] 1854 | name = "security-framework-sys" 1855 | version = "2.14.0" 1856 | source = "registry+https://github.com/rust-lang/crates.io-index" 1857 | checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" 1858 | dependencies = [ 1859 | "core-foundation-sys", 1860 | "libc", 1861 | ] 1862 | 1863 | [[package]] 1864 | name = "serde" 1865 | version = "1.0.219" 1866 | source = "registry+https://github.com/rust-lang/crates.io-index" 1867 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 1868 | dependencies = [ 1869 | "serde_derive", 1870 | ] 1871 | 1872 | [[package]] 1873 | name = "serde_derive" 1874 | version = "1.0.219" 1875 | source = "registry+https://github.com/rust-lang/crates.io-index" 1876 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 1877 | dependencies = [ 1878 | "proc-macro2", 1879 | "quote", 1880 | "syn", 1881 | ] 1882 | 1883 | [[package]] 1884 | name = "serde_json" 1885 | version = "1.0.140" 1886 | source = "registry+https://github.com/rust-lang/crates.io-index" 1887 | checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" 1888 | dependencies = [ 1889 | "itoa", 1890 | "memchr", 1891 | "ryu", 1892 | "serde", 1893 | ] 1894 | 1895 | [[package]] 1896 | name = "serde_urlencoded" 1897 | version = "0.7.1" 1898 | source = "registry+https://github.com/rust-lang/crates.io-index" 1899 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1900 | dependencies = [ 1901 | "form_urlencoded", 1902 | "itoa", 1903 | "ryu", 1904 | "serde", 1905 | ] 1906 | 1907 | [[package]] 1908 | name = "sha1" 1909 | version = "0.10.6" 1910 | source = "registry+https://github.com/rust-lang/crates.io-index" 1911 | checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" 1912 | dependencies = [ 1913 | "cfg-if", 1914 | "cpufeatures", 1915 | "digest", 1916 | ] 1917 | 1918 | [[package]] 1919 | name = "sha2" 1920 | version = "0.10.9" 1921 | source = "registry+https://github.com/rust-lang/crates.io-index" 1922 | checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" 1923 | dependencies = [ 1924 | "cfg-if", 1925 | "cpufeatures", 1926 | "digest", 1927 | ] 1928 | 1929 | [[package]] 1930 | name = "shlex" 1931 | version = "1.3.0" 1932 | source = "registry+https://github.com/rust-lang/crates.io-index" 1933 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1934 | 1935 | [[package]] 1936 | name = "signal-hook-registry" 1937 | version = "1.4.5" 1938 | source = "registry+https://github.com/rust-lang/crates.io-index" 1939 | checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" 1940 | dependencies = [ 1941 | "libc", 1942 | ] 1943 | 1944 | [[package]] 1945 | name = "slab" 1946 | version = "0.4.9" 1947 | source = "registry+https://github.com/rust-lang/crates.io-index" 1948 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1949 | dependencies = [ 1950 | "autocfg", 1951 | ] 1952 | 1953 | [[package]] 1954 | name = "smallvec" 1955 | version = "1.15.0" 1956 | source = "registry+https://github.com/rust-lang/crates.io-index" 1957 | checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" 1958 | 1959 | [[package]] 1960 | name = "socket2" 1961 | version = "0.5.10" 1962 | source = "registry+https://github.com/rust-lang/crates.io-index" 1963 | checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" 1964 | dependencies = [ 1965 | "libc", 1966 | "windows-sys 0.52.0", 1967 | ] 1968 | 1969 | [[package]] 1970 | name = "spin" 1971 | version = "0.9.8" 1972 | source = "registry+https://github.com/rust-lang/crates.io-index" 1973 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 1974 | 1975 | [[package]] 1976 | name = "stable_deref_trait" 1977 | version = "1.2.0" 1978 | source = "registry+https://github.com/rust-lang/crates.io-index" 1979 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1980 | 1981 | [[package]] 1982 | name = "strsim" 1983 | version = "0.11.1" 1984 | source = "registry+https://github.com/rust-lang/crates.io-index" 1985 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1986 | 1987 | [[package]] 1988 | name = "subtle" 1989 | version = "2.6.1" 1990 | source = "registry+https://github.com/rust-lang/crates.io-index" 1991 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 1992 | 1993 | [[package]] 1994 | name = "symlink" 1995 | version = "0.1.0" 1996 | source = "registry+https://github.com/rust-lang/crates.io-index" 1997 | checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" 1998 | 1999 | [[package]] 2000 | name = "syn" 2001 | version = "2.0.101" 2002 | source = "registry+https://github.com/rust-lang/crates.io-index" 2003 | checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" 2004 | dependencies = [ 2005 | "proc-macro2", 2006 | "quote", 2007 | "unicode-ident", 2008 | ] 2009 | 2010 | [[package]] 2011 | name = "sync_wrapper" 2012 | version = "0.1.2" 2013 | source = "registry+https://github.com/rust-lang/crates.io-index" 2014 | checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" 2015 | 2016 | [[package]] 2017 | name = "synstructure" 2018 | version = "0.13.2" 2019 | source = "registry+https://github.com/rust-lang/crates.io-index" 2020 | checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" 2021 | dependencies = [ 2022 | "proc-macro2", 2023 | "quote", 2024 | "syn", 2025 | ] 2026 | 2027 | [[package]] 2028 | name = "system-configuration" 2029 | version = "0.5.1" 2030 | source = "registry+https://github.com/rust-lang/crates.io-index" 2031 | checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" 2032 | dependencies = [ 2033 | "bitflags 1.3.2", 2034 | "core-foundation", 2035 | "system-configuration-sys", 2036 | ] 2037 | 2038 | [[package]] 2039 | name = "system-configuration-sys" 2040 | version = "0.5.0" 2041 | source = "registry+https://github.com/rust-lang/crates.io-index" 2042 | checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" 2043 | dependencies = [ 2044 | "core-foundation-sys", 2045 | "libc", 2046 | ] 2047 | 2048 | [[package]] 2049 | name = "tempfile" 2050 | version = "3.20.0" 2051 | source = "registry+https://github.com/rust-lang/crates.io-index" 2052 | checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" 2053 | dependencies = [ 2054 | "fastrand", 2055 | "getrandom 0.3.3", 2056 | "once_cell", 2057 | "rustix", 2058 | "windows-sys 0.59.0", 2059 | ] 2060 | 2061 | [[package]] 2062 | name = "termcolor" 2063 | version = "1.4.1" 2064 | source = "registry+https://github.com/rust-lang/crates.io-index" 2065 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 2066 | dependencies = [ 2067 | "winapi-util", 2068 | ] 2069 | 2070 | [[package]] 2071 | name = "thiserror" 2072 | version = "1.0.69" 2073 | source = "registry+https://github.com/rust-lang/crates.io-index" 2074 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 2075 | dependencies = [ 2076 | "thiserror-impl 1.0.69", 2077 | ] 2078 | 2079 | [[package]] 2080 | name = "thiserror" 2081 | version = "2.0.12" 2082 | source = "registry+https://github.com/rust-lang/crates.io-index" 2083 | checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" 2084 | dependencies = [ 2085 | "thiserror-impl 2.0.12", 2086 | ] 2087 | 2088 | [[package]] 2089 | name = "thiserror-impl" 2090 | version = "1.0.69" 2091 | source = "registry+https://github.com/rust-lang/crates.io-index" 2092 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 2093 | dependencies = [ 2094 | "proc-macro2", 2095 | "quote", 2096 | "syn", 2097 | ] 2098 | 2099 | [[package]] 2100 | name = "thiserror-impl" 2101 | version = "2.0.12" 2102 | source = "registry+https://github.com/rust-lang/crates.io-index" 2103 | checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" 2104 | dependencies = [ 2105 | "proc-macro2", 2106 | "quote", 2107 | "syn", 2108 | ] 2109 | 2110 | [[package]] 2111 | name = "tinystr" 2112 | version = "0.8.1" 2113 | source = "registry+https://github.com/rust-lang/crates.io-index" 2114 | checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" 2115 | dependencies = [ 2116 | "displaydoc", 2117 | "zerovec", 2118 | ] 2119 | 2120 | [[package]] 2121 | name = "tinyvec" 2122 | version = "1.9.0" 2123 | source = "registry+https://github.com/rust-lang/crates.io-index" 2124 | checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" 2125 | dependencies = [ 2126 | "tinyvec_macros", 2127 | ] 2128 | 2129 | [[package]] 2130 | name = "tinyvec_macros" 2131 | version = "0.1.1" 2132 | source = "registry+https://github.com/rust-lang/crates.io-index" 2133 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 2134 | 2135 | [[package]] 2136 | name = "tokio" 2137 | version = "1.45.1" 2138 | source = "registry+https://github.com/rust-lang/crates.io-index" 2139 | checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" 2140 | dependencies = [ 2141 | "backtrace", 2142 | "bytes", 2143 | "libc", 2144 | "mio", 2145 | "pin-project-lite", 2146 | "socket2", 2147 | "tokio-macros", 2148 | "windows-sys 0.52.0", 2149 | ] 2150 | 2151 | [[package]] 2152 | name = "tokio-macros" 2153 | version = "2.5.0" 2154 | source = "registry+https://github.com/rust-lang/crates.io-index" 2155 | checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" 2156 | dependencies = [ 2157 | "proc-macro2", 2158 | "quote", 2159 | "syn", 2160 | ] 2161 | 2162 | [[package]] 2163 | name = "tokio-native-tls" 2164 | version = "0.3.1" 2165 | source = "registry+https://github.com/rust-lang/crates.io-index" 2166 | checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" 2167 | dependencies = [ 2168 | "native-tls", 2169 | "tokio", 2170 | ] 2171 | 2172 | [[package]] 2173 | name = "tokio-tungstenite" 2174 | version = "0.21.0" 2175 | source = "registry+https://github.com/rust-lang/crates.io-index" 2176 | checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" 2177 | dependencies = [ 2178 | "futures-util", 2179 | "log", 2180 | "tokio", 2181 | "tungstenite", 2182 | ] 2183 | 2184 | [[package]] 2185 | name = "tokio-util" 2186 | version = "0.7.15" 2187 | source = "registry+https://github.com/rust-lang/crates.io-index" 2188 | checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" 2189 | dependencies = [ 2190 | "bytes", 2191 | "futures-core", 2192 | "futures-sink", 2193 | "pin-project-lite", 2194 | "tokio", 2195 | ] 2196 | 2197 | [[package]] 2198 | name = "tower-service" 2199 | version = "0.3.3" 2200 | source = "registry+https://github.com/rust-lang/crates.io-index" 2201 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 2202 | 2203 | [[package]] 2204 | name = "tracing" 2205 | version = "0.1.41" 2206 | source = "registry+https://github.com/rust-lang/crates.io-index" 2207 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 2208 | dependencies = [ 2209 | "log", 2210 | "pin-project-lite", 2211 | "tracing-attributes", 2212 | "tracing-core", 2213 | ] 2214 | 2215 | [[package]] 2216 | name = "tracing-attributes" 2217 | version = "0.1.28" 2218 | source = "registry+https://github.com/rust-lang/crates.io-index" 2219 | checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" 2220 | dependencies = [ 2221 | "proc-macro2", 2222 | "quote", 2223 | "syn", 2224 | ] 2225 | 2226 | [[package]] 2227 | name = "tracing-core" 2228 | version = "0.1.33" 2229 | source = "registry+https://github.com/rust-lang/crates.io-index" 2230 | checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" 2231 | dependencies = [ 2232 | "once_cell", 2233 | ] 2234 | 2235 | [[package]] 2236 | name = "try-lock" 2237 | version = "0.2.5" 2238 | source = "registry+https://github.com/rust-lang/crates.io-index" 2239 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 2240 | 2241 | [[package]] 2242 | name = "tungstenite" 2243 | version = "0.21.0" 2244 | source = "registry+https://github.com/rust-lang/crates.io-index" 2245 | checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" 2246 | dependencies = [ 2247 | "byteorder", 2248 | "bytes", 2249 | "data-encoding", 2250 | "http 1.3.1", 2251 | "httparse", 2252 | "log", 2253 | "rand", 2254 | "sha1", 2255 | "thiserror 1.0.69", 2256 | "url", 2257 | "utf-8", 2258 | ] 2259 | 2260 | [[package]] 2261 | name = "typenum" 2262 | version = "1.18.0" 2263 | source = "registry+https://github.com/rust-lang/crates.io-index" 2264 | checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" 2265 | 2266 | [[package]] 2267 | name = "unicase" 2268 | version = "2.8.1" 2269 | source = "registry+https://github.com/rust-lang/crates.io-index" 2270 | checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" 2271 | 2272 | [[package]] 2273 | name = "unicode-ident" 2274 | version = "1.0.18" 2275 | source = "registry+https://github.com/rust-lang/crates.io-index" 2276 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 2277 | 2278 | [[package]] 2279 | name = "unicode-normalization" 2280 | version = "0.1.24" 2281 | source = "registry+https://github.com/rust-lang/crates.io-index" 2282 | checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" 2283 | dependencies = [ 2284 | "tinyvec", 2285 | ] 2286 | 2287 | [[package]] 2288 | name = "url" 2289 | version = "2.5.4" 2290 | source = "registry+https://github.com/rust-lang/crates.io-index" 2291 | checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" 2292 | dependencies = [ 2293 | "form_urlencoded", 2294 | "idna", 2295 | "percent-encoding", 2296 | ] 2297 | 2298 | [[package]] 2299 | name = "urlencoding" 2300 | version = "2.1.3" 2301 | source = "registry+https://github.com/rust-lang/crates.io-index" 2302 | checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" 2303 | 2304 | [[package]] 2305 | name = "utf-8" 2306 | version = "0.7.6" 2307 | source = "registry+https://github.com/rust-lang/crates.io-index" 2308 | checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 2309 | 2310 | [[package]] 2311 | name = "utf8_iter" 2312 | version = "1.0.4" 2313 | source = "registry+https://github.com/rust-lang/crates.io-index" 2314 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 2315 | 2316 | [[package]] 2317 | name = "utf8parse" 2318 | version = "0.2.2" 2319 | source = "registry+https://github.com/rust-lang/crates.io-index" 2320 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 2321 | 2322 | [[package]] 2323 | name = "value-bag" 2324 | version = "1.11.1" 2325 | source = "registry+https://github.com/rust-lang/crates.io-index" 2326 | checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5" 2327 | 2328 | [[package]] 2329 | name = "vcpkg" 2330 | version = "0.2.15" 2331 | source = "registry+https://github.com/rust-lang/crates.io-index" 2332 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 2333 | 2334 | [[package]] 2335 | name = "version_check" 2336 | version = "0.9.5" 2337 | source = "registry+https://github.com/rust-lang/crates.io-index" 2338 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 2339 | 2340 | [[package]] 2341 | name = "want" 2342 | version = "0.3.1" 2343 | source = "registry+https://github.com/rust-lang/crates.io-index" 2344 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 2345 | dependencies = [ 2346 | "try-lock", 2347 | ] 2348 | 2349 | [[package]] 2350 | name = "warp" 2351 | version = "0.3.7" 2352 | source = "registry+https://github.com/rust-lang/crates.io-index" 2353 | checksum = "4378d202ff965b011c64817db11d5829506d3404edeadb61f190d111da3f231c" 2354 | dependencies = [ 2355 | "bytes", 2356 | "futures-channel", 2357 | "futures-util", 2358 | "headers", 2359 | "http 0.2.12", 2360 | "hyper", 2361 | "log", 2362 | "mime", 2363 | "mime_guess", 2364 | "multer", 2365 | "percent-encoding", 2366 | "pin-project", 2367 | "scoped-tls", 2368 | "serde", 2369 | "serde_json", 2370 | "serde_urlencoded", 2371 | "tokio", 2372 | "tokio-tungstenite", 2373 | "tokio-util", 2374 | "tower-service", 2375 | "tracing", 2376 | ] 2377 | 2378 | [[package]] 2379 | name = "wasi" 2380 | version = "0.11.0+wasi-snapshot-preview1" 2381 | source = "registry+https://github.com/rust-lang/crates.io-index" 2382 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 2383 | 2384 | [[package]] 2385 | name = "wasi" 2386 | version = "0.14.2+wasi-0.2.4" 2387 | source = "registry+https://github.com/rust-lang/crates.io-index" 2388 | checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" 2389 | dependencies = [ 2390 | "wit-bindgen-rt", 2391 | ] 2392 | 2393 | [[package]] 2394 | name = "wasm-bindgen" 2395 | version = "0.2.100" 2396 | source = "registry+https://github.com/rust-lang/crates.io-index" 2397 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 2398 | dependencies = [ 2399 | "cfg-if", 2400 | "once_cell", 2401 | "rustversion", 2402 | "wasm-bindgen-macro", 2403 | ] 2404 | 2405 | [[package]] 2406 | name = "wasm-bindgen-backend" 2407 | version = "0.2.100" 2408 | source = "registry+https://github.com/rust-lang/crates.io-index" 2409 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 2410 | dependencies = [ 2411 | "bumpalo", 2412 | "log", 2413 | "proc-macro2", 2414 | "quote", 2415 | "syn", 2416 | "wasm-bindgen-shared", 2417 | ] 2418 | 2419 | [[package]] 2420 | name = "wasm-bindgen-futures" 2421 | version = "0.4.50" 2422 | source = "registry+https://github.com/rust-lang/crates.io-index" 2423 | checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" 2424 | dependencies = [ 2425 | "cfg-if", 2426 | "js-sys", 2427 | "once_cell", 2428 | "wasm-bindgen", 2429 | "web-sys", 2430 | ] 2431 | 2432 | [[package]] 2433 | name = "wasm-bindgen-macro" 2434 | version = "0.2.100" 2435 | source = "registry+https://github.com/rust-lang/crates.io-index" 2436 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 2437 | dependencies = [ 2438 | "quote", 2439 | "wasm-bindgen-macro-support", 2440 | ] 2441 | 2442 | [[package]] 2443 | name = "wasm-bindgen-macro-support" 2444 | version = "0.2.100" 2445 | source = "registry+https://github.com/rust-lang/crates.io-index" 2446 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 2447 | dependencies = [ 2448 | "proc-macro2", 2449 | "quote", 2450 | "syn", 2451 | "wasm-bindgen-backend", 2452 | "wasm-bindgen-shared", 2453 | ] 2454 | 2455 | [[package]] 2456 | name = "wasm-bindgen-shared" 2457 | version = "0.2.100" 2458 | source = "registry+https://github.com/rust-lang/crates.io-index" 2459 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 2460 | dependencies = [ 2461 | "unicode-ident", 2462 | ] 2463 | 2464 | [[package]] 2465 | name = "wasm-streams" 2466 | version = "0.4.2" 2467 | source = "registry+https://github.com/rust-lang/crates.io-index" 2468 | checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" 2469 | dependencies = [ 2470 | "futures-util", 2471 | "js-sys", 2472 | "wasm-bindgen", 2473 | "wasm-bindgen-futures", 2474 | "web-sys", 2475 | ] 2476 | 2477 | [[package]] 2478 | name = "web-sys" 2479 | version = "0.3.77" 2480 | source = "registry+https://github.com/rust-lang/crates.io-index" 2481 | checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" 2482 | dependencies = [ 2483 | "js-sys", 2484 | "wasm-bindgen", 2485 | ] 2486 | 2487 | [[package]] 2488 | name = "winapi" 2489 | version = "0.3.9" 2490 | source = "registry+https://github.com/rust-lang/crates.io-index" 2491 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 2492 | dependencies = [ 2493 | "winapi-i686-pc-windows-gnu", 2494 | "winapi-x86_64-pc-windows-gnu", 2495 | ] 2496 | 2497 | [[package]] 2498 | name = "winapi-i686-pc-windows-gnu" 2499 | version = "0.4.0" 2500 | source = "registry+https://github.com/rust-lang/crates.io-index" 2501 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 2502 | 2503 | [[package]] 2504 | name = "winapi-util" 2505 | version = "0.1.9" 2506 | source = "registry+https://github.com/rust-lang/crates.io-index" 2507 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 2508 | dependencies = [ 2509 | "windows-sys 0.59.0", 2510 | ] 2511 | 2512 | [[package]] 2513 | name = "winapi-x86_64-pc-windows-gnu" 2514 | version = "0.4.0" 2515 | source = "registry+https://github.com/rust-lang/crates.io-index" 2516 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 2517 | 2518 | [[package]] 2519 | name = "windows-core" 2520 | version = "0.61.2" 2521 | source = "registry+https://github.com/rust-lang/crates.io-index" 2522 | checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" 2523 | dependencies = [ 2524 | "windows-implement", 2525 | "windows-interface", 2526 | "windows-link", 2527 | "windows-result", 2528 | "windows-strings", 2529 | ] 2530 | 2531 | [[package]] 2532 | name = "windows-implement" 2533 | version = "0.60.0" 2534 | source = "registry+https://github.com/rust-lang/crates.io-index" 2535 | checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" 2536 | dependencies = [ 2537 | "proc-macro2", 2538 | "quote", 2539 | "syn", 2540 | ] 2541 | 2542 | [[package]] 2543 | name = "windows-interface" 2544 | version = "0.59.1" 2545 | source = "registry+https://github.com/rust-lang/crates.io-index" 2546 | checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" 2547 | dependencies = [ 2548 | "proc-macro2", 2549 | "quote", 2550 | "syn", 2551 | ] 2552 | 2553 | [[package]] 2554 | name = "windows-link" 2555 | version = "0.1.1" 2556 | source = "registry+https://github.com/rust-lang/crates.io-index" 2557 | checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" 2558 | 2559 | [[package]] 2560 | name = "windows-result" 2561 | version = "0.3.4" 2562 | source = "registry+https://github.com/rust-lang/crates.io-index" 2563 | checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" 2564 | dependencies = [ 2565 | "windows-link", 2566 | ] 2567 | 2568 | [[package]] 2569 | name = "windows-strings" 2570 | version = "0.4.2" 2571 | source = "registry+https://github.com/rust-lang/crates.io-index" 2572 | checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" 2573 | dependencies = [ 2574 | "windows-link", 2575 | ] 2576 | 2577 | [[package]] 2578 | name = "windows-sys" 2579 | version = "0.48.0" 2580 | source = "registry+https://github.com/rust-lang/crates.io-index" 2581 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 2582 | dependencies = [ 2583 | "windows-targets 0.48.5", 2584 | ] 2585 | 2586 | [[package]] 2587 | name = "windows-sys" 2588 | version = "0.52.0" 2589 | source = "registry+https://github.com/rust-lang/crates.io-index" 2590 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 2591 | dependencies = [ 2592 | "windows-targets 0.52.6", 2593 | ] 2594 | 2595 | [[package]] 2596 | name = "windows-sys" 2597 | version = "0.59.0" 2598 | source = "registry+https://github.com/rust-lang/crates.io-index" 2599 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 2600 | dependencies = [ 2601 | "windows-targets 0.52.6", 2602 | ] 2603 | 2604 | [[package]] 2605 | name = "windows-targets" 2606 | version = "0.48.5" 2607 | source = "registry+https://github.com/rust-lang/crates.io-index" 2608 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 2609 | dependencies = [ 2610 | "windows_aarch64_gnullvm 0.48.5", 2611 | "windows_aarch64_msvc 0.48.5", 2612 | "windows_i686_gnu 0.48.5", 2613 | "windows_i686_msvc 0.48.5", 2614 | "windows_x86_64_gnu 0.48.5", 2615 | "windows_x86_64_gnullvm 0.48.5", 2616 | "windows_x86_64_msvc 0.48.5", 2617 | ] 2618 | 2619 | [[package]] 2620 | name = "windows-targets" 2621 | version = "0.52.6" 2622 | source = "registry+https://github.com/rust-lang/crates.io-index" 2623 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 2624 | dependencies = [ 2625 | "windows_aarch64_gnullvm 0.52.6", 2626 | "windows_aarch64_msvc 0.52.6", 2627 | "windows_i686_gnu 0.52.6", 2628 | "windows_i686_gnullvm", 2629 | "windows_i686_msvc 0.52.6", 2630 | "windows_x86_64_gnu 0.52.6", 2631 | "windows_x86_64_gnullvm 0.52.6", 2632 | "windows_x86_64_msvc 0.52.6", 2633 | ] 2634 | 2635 | [[package]] 2636 | name = "windows_aarch64_gnullvm" 2637 | version = "0.48.5" 2638 | source = "registry+https://github.com/rust-lang/crates.io-index" 2639 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 2640 | 2641 | [[package]] 2642 | name = "windows_aarch64_gnullvm" 2643 | version = "0.52.6" 2644 | source = "registry+https://github.com/rust-lang/crates.io-index" 2645 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 2646 | 2647 | [[package]] 2648 | name = "windows_aarch64_msvc" 2649 | version = "0.48.5" 2650 | source = "registry+https://github.com/rust-lang/crates.io-index" 2651 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 2652 | 2653 | [[package]] 2654 | name = "windows_aarch64_msvc" 2655 | version = "0.52.6" 2656 | source = "registry+https://github.com/rust-lang/crates.io-index" 2657 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 2658 | 2659 | [[package]] 2660 | name = "windows_i686_gnu" 2661 | version = "0.48.5" 2662 | source = "registry+https://github.com/rust-lang/crates.io-index" 2663 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 2664 | 2665 | [[package]] 2666 | name = "windows_i686_gnu" 2667 | version = "0.52.6" 2668 | source = "registry+https://github.com/rust-lang/crates.io-index" 2669 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 2670 | 2671 | [[package]] 2672 | name = "windows_i686_gnullvm" 2673 | version = "0.52.6" 2674 | source = "registry+https://github.com/rust-lang/crates.io-index" 2675 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 2676 | 2677 | [[package]] 2678 | name = "windows_i686_msvc" 2679 | version = "0.48.5" 2680 | source = "registry+https://github.com/rust-lang/crates.io-index" 2681 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 2682 | 2683 | [[package]] 2684 | name = "windows_i686_msvc" 2685 | version = "0.52.6" 2686 | source = "registry+https://github.com/rust-lang/crates.io-index" 2687 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 2688 | 2689 | [[package]] 2690 | name = "windows_x86_64_gnu" 2691 | version = "0.48.5" 2692 | source = "registry+https://github.com/rust-lang/crates.io-index" 2693 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 2694 | 2695 | [[package]] 2696 | name = "windows_x86_64_gnu" 2697 | version = "0.52.6" 2698 | source = "registry+https://github.com/rust-lang/crates.io-index" 2699 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 2700 | 2701 | [[package]] 2702 | name = "windows_x86_64_gnullvm" 2703 | version = "0.48.5" 2704 | source = "registry+https://github.com/rust-lang/crates.io-index" 2705 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 2706 | 2707 | [[package]] 2708 | name = "windows_x86_64_gnullvm" 2709 | version = "0.52.6" 2710 | source = "registry+https://github.com/rust-lang/crates.io-index" 2711 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 2712 | 2713 | [[package]] 2714 | name = "windows_x86_64_msvc" 2715 | version = "0.48.5" 2716 | source = "registry+https://github.com/rust-lang/crates.io-index" 2717 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 2718 | 2719 | [[package]] 2720 | name = "windows_x86_64_msvc" 2721 | version = "0.52.6" 2722 | source = "registry+https://github.com/rust-lang/crates.io-index" 2723 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 2724 | 2725 | [[package]] 2726 | name = "winreg" 2727 | version = "0.50.0" 2728 | source = "registry+https://github.com/rust-lang/crates.io-index" 2729 | checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" 2730 | dependencies = [ 2731 | "cfg-if", 2732 | "windows-sys 0.48.0", 2733 | ] 2734 | 2735 | [[package]] 2736 | name = "wit-bindgen-rt" 2737 | version = "0.39.0" 2738 | source = "registry+https://github.com/rust-lang/crates.io-index" 2739 | checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" 2740 | dependencies = [ 2741 | "bitflags 2.9.1", 2742 | ] 2743 | 2744 | [[package]] 2745 | name = "writeable" 2746 | version = "0.6.1" 2747 | source = "registry+https://github.com/rust-lang/crates.io-index" 2748 | checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" 2749 | 2750 | [[package]] 2751 | name = "xattr" 2752 | version = "0.2.3" 2753 | source = "registry+https://github.com/rust-lang/crates.io-index" 2754 | checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" 2755 | dependencies = [ 2756 | "libc", 2757 | ] 2758 | 2759 | [[package]] 2760 | name = "yoke" 2761 | version = "0.8.0" 2762 | source = "registry+https://github.com/rust-lang/crates.io-index" 2763 | checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" 2764 | dependencies = [ 2765 | "serde", 2766 | "stable_deref_trait", 2767 | "yoke-derive", 2768 | "zerofrom", 2769 | ] 2770 | 2771 | [[package]] 2772 | name = "yoke-derive" 2773 | version = "0.8.0" 2774 | source = "registry+https://github.com/rust-lang/crates.io-index" 2775 | checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" 2776 | dependencies = [ 2777 | "proc-macro2", 2778 | "quote", 2779 | "syn", 2780 | "synstructure", 2781 | ] 2782 | 2783 | [[package]] 2784 | name = "zerocopy" 2785 | version = "0.8.25" 2786 | source = "registry+https://github.com/rust-lang/crates.io-index" 2787 | checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" 2788 | dependencies = [ 2789 | "zerocopy-derive", 2790 | ] 2791 | 2792 | [[package]] 2793 | name = "zerocopy-derive" 2794 | version = "0.8.25" 2795 | source = "registry+https://github.com/rust-lang/crates.io-index" 2796 | checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" 2797 | dependencies = [ 2798 | "proc-macro2", 2799 | "quote", 2800 | "syn", 2801 | ] 2802 | 2803 | [[package]] 2804 | name = "zerofrom" 2805 | version = "0.1.6" 2806 | source = "registry+https://github.com/rust-lang/crates.io-index" 2807 | checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" 2808 | dependencies = [ 2809 | "zerofrom-derive", 2810 | ] 2811 | 2812 | [[package]] 2813 | name = "zerofrom-derive" 2814 | version = "0.1.6" 2815 | source = "registry+https://github.com/rust-lang/crates.io-index" 2816 | checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" 2817 | dependencies = [ 2818 | "proc-macro2", 2819 | "quote", 2820 | "syn", 2821 | "synstructure", 2822 | ] 2823 | 2824 | [[package]] 2825 | name = "zerotrie" 2826 | version = "0.2.2" 2827 | source = "registry+https://github.com/rust-lang/crates.io-index" 2828 | checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" 2829 | dependencies = [ 2830 | "displaydoc", 2831 | "yoke", 2832 | "zerofrom", 2833 | ] 2834 | 2835 | [[package]] 2836 | name = "zerovec" 2837 | version = "0.11.2" 2838 | source = "registry+https://github.com/rust-lang/crates.io-index" 2839 | checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" 2840 | dependencies = [ 2841 | "yoke", 2842 | "zerofrom", 2843 | "zerovec-derive", 2844 | ] 2845 | 2846 | [[package]] 2847 | name = "zerovec-derive" 2848 | version = "0.11.1" 2849 | source = "registry+https://github.com/rust-lang/crates.io-index" 2850 | checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" 2851 | dependencies = [ 2852 | "proc-macro2", 2853 | "quote", 2854 | "syn", 2855 | ] 2856 | 2857 | [[package]] 2858 | name = "zstd" 2859 | version = "0.13.3" 2860 | source = "registry+https://github.com/rust-lang/crates.io-index" 2861 | checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" 2862 | dependencies = [ 2863 | "zstd-safe", 2864 | ] 2865 | 2866 | [[package]] 2867 | name = "zstd-safe" 2868 | version = "7.2.4" 2869 | source = "registry+https://github.com/rust-lang/crates.io-index" 2870 | checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" 2871 | dependencies = [ 2872 | "zstd-sys", 2873 | ] 2874 | 2875 | [[package]] 2876 | name = "zstd-sys" 2877 | version = "2.0.15+zstd.1.5.7" 2878 | source = "registry+https://github.com/rust-lang/crates.io-index" 2879 | checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" 2880 | dependencies = [ 2881 | "cc", 2882 | "pkg-config", 2883 | ] 2884 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "oranc" 3 | authors = [ "Lin Yinfeng " ] 4 | version = "0.1.0" 5 | edition = "2024" 6 | license = "MIT" 7 | description = """ 8 | OCI Registry As Nix Cache 9 | """ 10 | 11 | [dependencies] 12 | warp = "*" 13 | # wait for warp with hyper and http 1.0 14 | hyper = "0" 15 | http = "0" 16 | reqwest = "0.11" 17 | oci-distribution = "0.10" 18 | clap = { version = "*", features = [ "cargo", "derive" ] } 19 | clap_complete = "*" 20 | tokio = {version = "*", features = [ "macros", "rt-multi-thread" ] } 21 | futures = "*" 22 | tokio-util = {version = "*", features = [ ] } 23 | log = "*" 24 | pretty_env_logger = "*" 25 | thiserror = "*" 26 | regex = "*" 27 | once_cell = "*" 28 | data-encoding = "*" 29 | async-tar = "*" 30 | maplit = "*" 31 | bytes = "*" 32 | rusqlite = "*" 33 | nix-nar = "*" 34 | zstd = "*" 35 | nix-base32 = "*" 36 | sha2 = "*" 37 | tempfile = "*" 38 | ed25519-compact = "*" 39 | urlencoding = "*" 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Lin Yinfeng 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # oranc 2 | 3 | OCI Registry As Nix Cache. 4 | 5 | Use an OCI registry (typically, [ghcr.io](https://ghcr.io)) to distribute binary caches of your Nix packages! 6 | 7 | ## Warning 8 | 9 | 1. Tags, image manifests, and layers created by oranc are so different from other typical OCI repositories. 10 | So I don't know if it is an abuse of OCI registries. Pushing to [ghcr.io](https://ghcr.io) may violate the terms of service of GitHub. 11 | 12 | 2. Repository schema of oranc is still unstable. 13 | 14 | Tag encoding has been updated to support CA realisations. To use old pushed cache, please use the `--fallback-encodings base32-dnssec` option. 15 | 16 | ```console 17 | $ oranc tag encode "realisations/sha256:67890e0958e5d1a2944a3389151472a9acde025c7812f68381a7eef0d82152d1!libgcc.doi" 18 | realisations_L_sha256_W_67890e0958e5d1a2944a3389151472a9acde025c7812f68381a7eef0d82152d1_x_libgcc.doi 19 | $ oranc tag encode "realisations/sha256:67890e0958e5d1a2944a3389151472a9acde025c7812f68381a7eef0d82152d1!libgcc.doi" \ 20 | --fallbacks --fallback-encodings base32-dnssec 21 | realisations_L_sha256_W_67890e0958e5d1a2944a3389151472a9acde025c7812f68381a7eef0d82152d1_x_libgcc.doi 22 | e9im2r39edgn8qbfdppiusr8c4p3adhq6orjge9gcko3id9ockqm8cb168sj8d316cpjge9h6koj8dpic4sm2or4cko34db36ss32cj66os36e1hc4rmapb661i3gchh6kp68c91dhkm4pr3ccn68rr9 23 | ``` 24 | 25 | The `base32-dnssec` encoding for realisation is too long to fit into an OCI reference tag. 26 | 27 | ## Usage 28 | 29 | ### Push to OCI registry 30 | 31 | There are two different ways to push cache to OCI registry using oranc. 32 | 33 | * Push using `nix copy` with oranc server. 34 | * Direct push using `oranc push` (faster but has some limitations). 35 | 36 | #### Push with nix copy and oranc server 37 | 38 | 1. Host a oranc server, or try [oranc.li7g.com](https://oranc.li7g.com). It's better to self-host an instance. If you do so, please replace all `oranc.li7g.com` below with your instance. 39 | 40 | ```bash 41 | oranc server --listen "{LISTEN_ADDRESS}:{LISTEN_PORT}" 42 | ``` 43 | 44 | 2. Set your credentials. 45 | 46 | ```bash 47 | export ORANC_USERNAME={YOUR_OCI_REGISTRY_USERNAME} 48 | export ORANC_PASSWORD={YOUR_OCI_REGISTRY_PASSWORD} 49 | export AWS_ACCESS_KEY_ID=$(echo -n "$ORANC_USERNAME:$ORANC_PASSWORD" | base64 --wrap 0) 50 | export AWS_SECRET_ACCESS_KEY="_" 51 | ``` 52 | 53 | 3. Build something. 54 | 55 | ```bash 56 | nix build 57 | ``` 58 | 59 | 4. Push to your OCI registry. 60 | 61 | ```bash 62 | nix copy --to "s3://{OCI_REPOSITORY_PART2}?endpoint=https://oranc.li7g.com/{OCI_REGISTRY}/{OCI_REPOSITORY_PART1}" ./result 63 | ``` 64 | 65 | Cache will be pushed to `https://{OCI_REGISTRY}/{OCI_REPOSITORY_PART1}/{OCI_REPOSITORY_PART2}`. 66 | 67 | #### Direct push 68 | 69 | 1. Prepare your signing keys. 70 | 71 | ```console 72 | $ nix key generate-secret --key-name {KEY_NAME} > {PRIVATE_KEY_FILE} 73 | $ cat {PRIVATE_KEY_FILE} | nix key convert-secret-to-public 74 | {PUBLIC_KEY} 75 | ``` 76 | 77 | 2. Set your credentials. 78 | 79 | ```bash 80 | export ORANC_USERNAME={YOUR_OCI_REGISTRY_USERNAME} 81 | export ORANC_PASSWORD={YOUR_OCI_REGISTRY_PASSWORD} 82 | export ORANC_SIGNING_KEY={YOUR_NIX_SIGNING_KEY} 83 | ``` 84 | 85 | 3. Initialize your OCI registry. 86 | 87 | ```bash 88 | oranc push --registry {OCI_REGISTRY} --repository {OCI_REPOSITORY} initialize 89 | ``` 90 | 91 | *Make the repository public*, otherwise, caching will not work. 92 | 93 | 4. Build something. 94 | 95 | ```bash 96 | nix build 97 | ``` 98 | 99 | 5. Push to your OCI registry. 100 | 101 | ```bash 102 | # you need to have write permission to `/nix/var/nix/db` 103 | # or pass the argument `--allow-immutable-db` 104 | # see the Limitations section 105 | echo ./result | oranc push --registry {OCI_REGISTRY} --repository {OCI_REPOSITORY} 106 | ``` 107 | 108 | `oranc` will sign the NAR archive on the fly using `ORANC_SIGNING_KEY`. 109 | 110 | Note that: 111 | 112 | 1. only unsigned paths will be pushed, if you manually signed store paths, use the argument `--already-signed` to push them. 113 | 2. Currently `oranc` will not sign local paths, run `... | xargs nix store sign --recursive --key-file {YOUR_KEY_FILE}` to sign paths locally. 114 | 115 | Run `oranc push --help` for more options. 116 | 117 | #### Limitations 118 | 119 | 1. `oranc push` reads the SQLite database `/nix/var/nix/db/db.sqlite`. The directory containing the database, `/nix/var/nix/db`, is typically owned by root. To open the database, `oranc` must have permission to create WAL files under the directory. 120 | 121 | To avoid requiring root permission to do `oranc push`, if `oranc push` does not able to create files under `/nix/var/nix/db/db.sqlite` and the argument `--allow-immutable-db` is passed, it will open the database in `immutable=1` mode, if another process writes to the database, `oranc push --allow-immutable-db` may fail. 122 | 123 | 2. `oranc push` does not support pushing content-addressed realisations. 124 | 125 | ### Use OCI registries as substituters 126 | 127 | Try [oranc.li7g.com](https://oranc.li7g.com). It's better to self-host an instance. If you do so, please replace all `oranc.li7g.com` below with your instance. 128 | 129 | Add settings to `nix.conf`: 130 | 131 | ```text 132 | substituters = https://oranc.li7g.com/{OCI_REGISTRY}/{OCI_REPOSITORY} 133 | trusted-public-keys = {PUBLIC_KEY} 134 | ``` 135 | 136 | or use NixOS configuration: 137 | 138 | ```nix 139 | { ... }: 140 | { 141 | nix.settings = { 142 | substituters = [ "https://oranc.li7g.com/{OCI_REGISTRY}/{OCI_REPOSITORY}" ]; 143 | trusted-public-keys = [ "{PUBLIC_KEY}" ]; 144 | }; 145 | } 146 | ``` 147 | 148 | If your OCI registry requires authentication, HTTP basic authentication is supported: 149 | 150 | 1. Add username and password to the substituter URL: `https://{ORANC_USERNAME}:{ORANC_PASSWORD}@oranc.li7g.com/{OCI_REGISTRY}/{OCI_REPOSITORY}`. 151 | 2. Or use a netrc file . 152 | 153 | **Your credential will be sent to the oranc server.** If you don't trust my instance, please host your own instance. 154 | 155 | ## Host oranc server 156 | 157 | Simply run, 158 | 159 | ```bash 160 | oranc server --listen "{LISTEN_ADDRESS}:{LISTEN_PORT}" 161 | ``` 162 | 163 | Run `oranc server --help` for more options. 164 | 165 | A NixOS module (`github:linyinfeng/oranc#nixosModules.oranc`) and a nixpkgs overlay (`github:linyinfeng/oranc#overlays.oranc`) are provided. 166 | 167 | ## TODO 168 | 169 | [ ] Improve push performance of `oranc server`. 170 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "crane": { 4 | "locked": { 5 | "lastModified": 1748047550, 6 | "narHash": "sha256-t0qLLqb4C1rdtiY8IFRH5KIapTY/n3Lqt57AmxEv9mk=", 7 | "owner": "ipetkov", 8 | "repo": "crane", 9 | "rev": "b718a78696060df6280196a6f992d04c87a16aef", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "ipetkov", 14 | "repo": "crane", 15 | "type": "github" 16 | } 17 | }, 18 | "flake-compat": { 19 | "flake": false, 20 | "locked": { 21 | "lastModified": 1747046372, 22 | "narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=", 23 | "owner": "edolstra", 24 | "repo": "flake-compat", 25 | "rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885", 26 | "type": "github" 27 | }, 28 | "original": { 29 | "owner": "edolstra", 30 | "repo": "flake-compat", 31 | "type": "github" 32 | } 33 | }, 34 | "flake-parts": { 35 | "inputs": { 36 | "nixpkgs-lib": [ 37 | "nixpkgs" 38 | ] 39 | }, 40 | "locked": { 41 | "lastModified": 1743550720, 42 | "narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=", 43 | "owner": "hercules-ci", 44 | "repo": "flake-parts", 45 | "rev": "c621e8422220273271f52058f618c94e405bb0f5", 46 | "type": "github" 47 | }, 48 | "original": { 49 | "owner": "hercules-ci", 50 | "repo": "flake-parts", 51 | "type": "github" 52 | } 53 | }, 54 | "nixpkgs": { 55 | "locked": { 56 | "lastModified": 1748460289, 57 | "narHash": "sha256-7doLyJBzCllvqX4gszYtmZUToxKvMUrg45EUWaUYmBg=", 58 | "owner": "nixos", 59 | "repo": "nixpkgs", 60 | "rev": "96ec055edbe5ee227f28cdbc3f1ddf1df5965102", 61 | "type": "github" 62 | }, 63 | "original": { 64 | "owner": "nixos", 65 | "ref": "nixos-unstable", 66 | "repo": "nixpkgs", 67 | "type": "github" 68 | } 69 | }, 70 | "root": { 71 | "inputs": { 72 | "crane": "crane", 73 | "flake-compat": "flake-compat", 74 | "flake-parts": "flake-parts", 75 | "nixpkgs": "nixpkgs", 76 | "treefmt-nix": "treefmt-nix" 77 | } 78 | }, 79 | "treefmt-nix": { 80 | "inputs": { 81 | "nixpkgs": [ 82 | "nixpkgs" 83 | ] 84 | }, 85 | "locked": { 86 | "lastModified": 1748243702, 87 | "narHash": "sha256-9YzfeN8CB6SzNPyPm2XjRRqSixDopTapaRsnTpXUEY8=", 88 | "owner": "numtide", 89 | "repo": "treefmt-nix", 90 | "rev": "1f3f7b784643d488ba4bf315638b2b0a4c5fb007", 91 | "type": "github" 92 | }, 93 | "original": { 94 | "owner": "numtide", 95 | "repo": "treefmt-nix", 96 | "type": "github" 97 | } 98 | } 99 | }, 100 | "root": "root", 101 | "version": 7 102 | } 103 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | flake-parts.url = "github:hercules-ci/flake-parts"; 4 | flake-parts.inputs.nixpkgs-lib.follows = "nixpkgs"; 5 | 6 | nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; 7 | 8 | treefmt-nix.url = "github:numtide/treefmt-nix"; 9 | treefmt-nix.inputs.nixpkgs.follows = "nixpkgs"; 10 | 11 | crane.url = "github:ipetkov/crane"; 12 | 13 | flake-compat.url = "github:edolstra/flake-compat"; 14 | flake-compat.flake = false; 15 | }; 16 | 17 | outputs = 18 | inputs@{ flake-parts, ... }: 19 | flake-parts.lib.mkFlake { inherit inputs; } ( 20 | { 21 | self, 22 | inputs, 23 | lib, 24 | ... 25 | }: 26 | { 27 | systems = [ 28 | "x86_64-linux" 29 | "aarch64-linux" 30 | ]; 31 | imports = [ 32 | inputs.flake-parts.flakeModules.easyOverlay 33 | inputs.treefmt-nix.flakeModule 34 | ]; 35 | flake = { 36 | nixosModules.oranc = ./nixos/module.nix; 37 | }; 38 | perSystem = 39 | { 40 | config, 41 | self', 42 | pkgs, 43 | system, 44 | ... 45 | }: 46 | let 47 | craneLib = inputs.crane.mkLib pkgs; 48 | src = craneLib.cleanCargoSource (craneLib.path ./.); 49 | bareCommonArgs = { 50 | inherit src; 51 | nativeBuildInputs = with pkgs; [ 52 | pkg-config 53 | installShellFiles 54 | ]; 55 | buildInputs = with pkgs; [ 56 | openssl 57 | sqlite 58 | ]; 59 | # TODO https://github.com/ipetkov/crane/issues/385 60 | doNotLinkInheritedArtifacts = true; 61 | }; 62 | cargoArtifacts = craneLib.buildDepsOnly bareCommonArgs; 63 | commonArgs = bareCommonArgs // { 64 | inherit cargoArtifacts; 65 | }; 66 | in 67 | { 68 | packages = { 69 | oranc = craneLib.buildPackage ( 70 | commonArgs 71 | // { 72 | postInstall = '' 73 | installShellCompletion --cmd oranc \ 74 | --bash <($out/bin/oranc completion bash) \ 75 | --fish <($out/bin/oranc completion fish) \ 76 | --zsh <($out/bin/oranc completion zsh) 77 | ''; 78 | } 79 | ); 80 | default = config.packages.oranc; 81 | dockerImage = pkgs.dockerTools.buildImage { 82 | name = "oranc"; 83 | tag = self.sourceInfo.rev or null; 84 | copyToRoot = pkgs.buildEnv { 85 | name = "oranc-env"; 86 | paths = [ 87 | pkgs.dockerTools.caCertificates 88 | ]; 89 | }; 90 | config = { 91 | Entrypoint = [ 92 | "${pkgs.tini}/bin/tini" 93 | "--" 94 | ]; 95 | Cmd = 96 | let 97 | start = pkgs.writeShellScript "start-oranc" '' 98 | exec ${config.packages.oranc}/bin/oranc server \ 99 | --listen "[::]:80" \ 100 | $EXTRA_ARGS "$@" 101 | ''; 102 | in 103 | [ "${start}" ]; 104 | Env = [ 105 | "RUST_LOG=oranc=info" 106 | "EXTRA_ARGS=" 107 | ]; 108 | ExposedPorts = { 109 | "80/tcp" = { }; 110 | }; 111 | Labels = 112 | { 113 | "org.opencontainers.image.title" = "oranc"; 114 | "org.opencontainers.image.description" = "OCI Registry As Nix Cache"; 115 | "org.opencontainers.image.url" = "https://github.com/linyinfeng/oranc"; 116 | "org.opencontainers.image.source" = "https://github.com/linyinfeng/oranc"; 117 | "org.opencontainers.image.licenses" = "MIT"; 118 | } 119 | // lib.optionalAttrs (self.sourceInfo ? rev) { 120 | "org.opencontainers.image.revision" = self.sourceInfo.rev; 121 | }; 122 | }; 123 | }; 124 | integrationTestScript = pkgs.callPackage ./integration-test { 125 | inherit (config.packages) dockerImage oranc; 126 | }; 127 | }; 128 | overlayAttrs.oranc = config.packages.oranc; 129 | checks = { 130 | inherit (self'.packages) oranc dockerImage; 131 | doc = craneLib.cargoDoc commonArgs; 132 | fmt = craneLib.cargoFmt { inherit src; }; 133 | nextest = craneLib.cargoNextest commonArgs; 134 | clippy = craneLib.cargoClippy ( 135 | commonArgs 136 | // { 137 | cargoClippyExtraArgs = "--all-targets -- --deny warnings"; 138 | } 139 | ); 140 | }; 141 | treefmt = { 142 | projectRootFile = "flake.nix"; 143 | programs = { 144 | nixfmt.enable = true; 145 | rustfmt.enable = true; 146 | shfmt.enable = true; 147 | }; 148 | }; 149 | devShells.default = pkgs.mkShell { 150 | inputsFrom = lib.attrValues self'.checks; 151 | packages = with pkgs; [ 152 | rustup 153 | rust-analyzer 154 | ]; 155 | }; 156 | }; 157 | } 158 | ); 159 | } 160 | -------------------------------------------------------------------------------- /integration-test/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | stdenvNoCC, 3 | replaceVars, 4 | formats, 5 | dockerImage, 6 | oranc, 7 | dockerTools, 8 | nix, 9 | bash, 10 | tini, 11 | buildEnv, 12 | coreutils, 13 | curl, 14 | findutils, 15 | shadow, 16 | gnused, 17 | tcping-go, 18 | }: 19 | let 20 | packageForTest = "github:nixos/nixpkgs/nixos-unstable#coreutils"; 21 | composeFile = (formats.yaml { }).generate "container-compose-yml" { 22 | services = { 23 | registry = { 24 | image = "registry"; 25 | }; 26 | oranc = { 27 | image = "oranc:${dockerImage.imageTag}"; 28 | environment = [ 29 | "EXTRA_ARGS=--no-ssl" 30 | ]; 31 | depends_on = { 32 | registry = { 33 | condition = "service_started"; 34 | }; 35 | }; 36 | }; 37 | oranc-test-script = { 38 | image = "oranc-test-script:${dockerImage.imageTag}"; 39 | environment = [ 40 | "PACKAGE_FOR_TEST=${packageForTest}" 41 | ]; 42 | depends_on = { 43 | registry = { 44 | condition = "service_started"; 45 | }; 46 | oranc = { 47 | condition = "service_started"; 48 | }; 49 | }; 50 | }; 51 | }; 52 | }; 53 | testScript = replaceVars ./test.sh { 54 | inherit (stdenvNoCC) shell; 55 | }; 56 | testScriptDockerImage = dockerTools.buildImageWithNixDb { 57 | name = "oranc-test-script"; 58 | tag = dockerImage.imageTag; 59 | copyToRoot = buildEnv { 60 | name = "image-root"; 61 | paths = [ 62 | coreutils 63 | oranc 64 | nix 65 | curl 66 | findutils 67 | shadow 68 | tcping-go 69 | gnused 70 | dockerTools.caCertificates 71 | ]; 72 | }; 73 | config = { 74 | Entrypoint = [ 75 | "${tini}/bin/tini" 76 | "--" 77 | ]; 78 | Cmd = [ 79 | "${bash}/bin/bash" 80 | "${testScript}" 81 | ]; 82 | Env = [ 83 | "RUST_LOG=oranc=info" 84 | # required by nix 85 | "USER=nobody" 86 | ]; 87 | }; 88 | }; 89 | driver = replaceVars ./driver.sh { 90 | inherit (stdenvNoCC) shell; 91 | inherit composeFile dockerImage testScriptDockerImage; 92 | }; 93 | in 94 | stdenvNoCC.mkDerivation (self: { 95 | name = "oranc-integration-test"; 96 | dontUnpack = true; 97 | installPhase = '' 98 | install -D "${driver}" "$out/bin/${self.name}" 99 | ''; 100 | }) 101 | -------------------------------------------------------------------------------- /integration-test/driver.sh: -------------------------------------------------------------------------------- 1 | #!@shell@ 2 | 3 | set -e 4 | 5 | docker="docker" 6 | prog_docker_compose="docker-compose" 7 | if which podman >/dev/null; then 8 | prog_docker="podman" 9 | prog_docker_compose="podman-compose" 10 | fi 11 | docker_compose=("$prog_docker_compose" "--file" "@composeFile@") 12 | 13 | echo 14 | echo "build and load docker image..." 15 | echo 16 | 17 | sudo "$docker" load <"@dockerImage@" 18 | sudo "$docker" load <"@testScriptDockerImage@" 19 | 20 | function cleanup { 21 | echo 22 | echo "docker compose down..." 23 | echo 24 | 25 | sudo "${docker_compose[@]}" down 26 | } 27 | trap "cleanup" EXIT 28 | 29 | echo 30 | echo "docker compose up..." 31 | echo 32 | 33 | sudo "${docker_compose[@]}" up --detach 34 | 35 | echo 36 | echo "docker compose logs..." 37 | echo 38 | 39 | sudo "${docker_compose[@]}" logs oranc oranc-test-script --names --timestamps --follow 40 | -------------------------------------------------------------------------------- /integration-test/test.sh: -------------------------------------------------------------------------------- 1 | #!@shell@ 2 | 3 | set -e 4 | 5 | function info { 6 | echo -en "\e[32m" # Green 7 | echo -n "$@" 8 | echo -e "\e[0m" 9 | } 10 | 11 | function stage { 12 | echo 13 | info "--" "$@" 14 | echo 15 | } 16 | 17 | info "arguments:" 18 | info " PACKAGE_FOR_TEST: '$PACKAGE_FOR_TEST'" 19 | info " PATH: '$PATH'" 20 | info " RUST_LOG: '$RUST_LOG'" 21 | 22 | stage "setting up working directory..." 23 | 24 | dir="/tmp/oranc-integration-test" 25 | mkdir -p "$dir" 26 | cd "$dir" 27 | echo "working directory: '$PWD'" 28 | 29 | stage "setting up nix..." 30 | 31 | cat >nix.conf <>/etc/nix/nix.conf 37 | 38 | groupadd nixbld --system 39 | 40 | stage "show nix.conf..." 41 | 42 | nix show-config 43 | 44 | stage "generate temporary key pair" 45 | 46 | nix key generate-secret --key-name "oranc-test" >secret 47 | cat secret | nix key convert-secret-to-public >public 48 | echo "secret key for test: $(cat secret)" 49 | echo "public key for test: $(cat public)" 50 | 51 | stage "setting up variables..." 52 | 53 | registry="registry:5000" 54 | repository="test-user/oranc-cache" 55 | substituter="http://oranc/$registry/$repository" 56 | public_key="$(cat public)" 57 | export ORANC_SIGNING_KEY="$(cat secret)" 58 | 59 | stage "intialize registry" 60 | 61 | oranc push --no-ssl --registry "$registry" --repository "$repository" initialize 62 | curl "$substituter/nix-cache-info" -v 63 | 64 | stage "get test packages" 65 | 66 | nix path-info --derivation --recursive "$PACKAGE_FOR_TEST" >derivers 67 | nix build "$PACKAGE_FOR_TEST" --no-link --print-out-paths >derived 68 | cat derived | xargs nix path-info --recursive >derived_closure 69 | cat derivers derived_closure >store_paths 70 | 71 | echo "derivers: $(cat derivers | wc -l)" 72 | echo "derived: $(cat derived | wc -l)" 73 | echo "derived_closure: $(cat derived_closure | wc -l)" 74 | echo "store_paths: $(cat store_paths | wc -l)" 75 | 76 | stage "push to packages" 77 | 78 | echo "store paths count: $(cat store_paths | wc -l)" 79 | cat store_paths 80 | 81 | # sign first, then push with --already-signed 82 | # oranc will check its signature matches already exists signature 83 | cat derived_closure | xargs nix store sign --key-file secret --verbose 84 | cat store_paths | 85 | oranc push \ 86 | --no-ssl \ 87 | --registry "$registry" \ 88 | --repository "$repository" \ 89 | --excluded-signing-key-pattern '^$' \ 90 | --already-signed 91 | 92 | stage "verify remote store" 93 | 94 | cat store_paths | xargs nix store verify \ 95 | --store "$substituter" \ 96 | --trusted-public-keys "$public_key" \ 97 | --verbose 98 | 99 | stage "gc local store" 100 | 101 | nix store gc 102 | 103 | stage "get test package from registry" 104 | 105 | # instantiate derivations again 106 | nix path-info --derivation --recursive "$PACKAGE_FOR_TEST" >/dev/null 107 | cat derived | 108 | xargs nix build \ 109 | --no-link \ 110 | --max-jobs 0 \ 111 | --substituters "$substituter" \ 112 | --trusted-public-keys "$public_key" \ 113 | --verbose 114 | 115 | stage "verify local store" 116 | 117 | cat derived_closure | xargs nix store verify \ 118 | --trusted-public-keys "$public_key" \ 119 | --verbose 120 | -------------------------------------------------------------------------------- /nixos/module.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | pkgs, 4 | lib, 5 | ... 6 | }: 7 | let 8 | cfg = config.services.oranc; 9 | in 10 | { 11 | options = { 12 | services.oranc = { 13 | enable = lib.mkEnableOption "oranc"; 14 | package = lib.mkOption { 15 | type = lib.types.package; 16 | default = pkgs.oranc; 17 | defaultText = "pkgs.oranc"; 18 | description = '' 19 | Which oranc package to use. 20 | ''; 21 | }; 22 | listen = lib.mkOption { 23 | type = lib.types.str; 24 | default = "[::]:8080"; 25 | description = '' 26 | Socket address to listen on. 27 | ''; 28 | }; 29 | log = lib.mkOption { 30 | type = lib.types.str; 31 | default = "oranc=info"; 32 | description = '' 33 | Log configuration in RUST_LOG format. 34 | ''; 35 | }; 36 | extraArgs = lib.mkOption { 37 | type = with lib.types; listOf str; 38 | default = [ ]; 39 | description = '' 40 | Extra command-line arguments pass to oranc. 41 | ''; 42 | }; 43 | }; 44 | }; 45 | config = lib.mkIf cfg.enable { 46 | systemd.services.oranc = { 47 | script = '' 48 | ${cfg.package}/bin/oranc server --listen "${cfg.listen}" ${lib.escapeShellArgs cfg.extraArgs} 49 | ''; 50 | serviceConfig = { 51 | DynamicUser = true; 52 | }; 53 | environment.RUST_LOG = cfg.log; 54 | wantedBy = [ "multi-user.target" ]; 55 | }; 56 | }; 57 | } 58 | -------------------------------------------------------------------------------- /src/convert.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use clap::ValueEnum; 3 | use data_encoding::BASE32_DNSSEC; 4 | use once_cell::sync::Lazy; 5 | use std::collections::BTreeMap; 6 | 7 | use crate::error::Error; 8 | 9 | #[derive(Clone, Debug, Parser)] 10 | pub struct EncodingOptions { 11 | #[arg(long, value_enum, default_value = "custom")] 12 | pub tag_encoding: TagEncoding, 13 | #[arg(long)] 14 | pub fallback_encodings: Vec, 15 | } 16 | 17 | impl EncodingOptions { 18 | pub fn key_to_tag(&self, key: &str) -> (String, Vec) { 19 | let main = self.tag_encoding.key_to_tag(key); 20 | let fallbacks = self 21 | .fallback_encodings 22 | .iter() 23 | .map(|e| e.key_to_tag(key)) 24 | .collect(); 25 | (main, fallbacks) 26 | } 27 | pub fn tag_to_key(&self, tag: &str) -> Result { 28 | let mut errors = vec![]; 29 | let main = [self.tag_encoding]; 30 | let encodings = main.iter().chain(self.fallback_encodings.iter()); 31 | for e in encodings { 32 | match e.tag_to_key(tag) { 33 | Ok(r) => return Ok(r), 34 | Err(e) => errors.push(e), 35 | } 36 | } 37 | Err(Error::TagToKey(errors)) 38 | } 39 | } 40 | 41 | #[derive(Clone, Copy, Debug, ValueEnum)] 42 | pub enum TagEncoding { 43 | // A custom encoding 44 | Custom, 45 | // https://docs.rs/data-encoding/latest/data_encoding/constant.BASE32_DNSSEC.html 46 | // It uses a base32 extended hex alphabet. 47 | // It is case-insensitive when decoding and uses lowercase when encoding. 48 | // It does not use padding. 49 | Base32DNSSEC, 50 | } 51 | 52 | static CUSTOM_ENCODING: Lazy = Lazy::new(CustomEncoding::new); 53 | 54 | impl TagEncoding { 55 | pub fn key_to_tag(&self, key: &str) -> String { 56 | match self { 57 | TagEncoding::Custom => CUSTOM_ENCODING.encode(key), 58 | TagEncoding::Base32DNSSEC => BASE32_DNSSEC.encode(key.as_bytes()), 59 | } 60 | } 61 | 62 | pub fn tag_to_key(&self, tag: &str) -> Result { 63 | match self { 64 | TagEncoding::Custom => CUSTOM_ENCODING.decode(tag), 65 | TagEncoding::Base32DNSSEC => { 66 | Ok(String::from_utf8(BASE32_DNSSEC.decode(tag.as_bytes())?)?) 67 | } 68 | } 69 | } 70 | } 71 | 72 | /// A tag MUST be at most 128 characters in length and MUST match the following regular expression: 73 | /// [a-zA-Z0-9_][a-zA-Z0-9._-]{0,127} 74 | /// https://github.com/opencontainers/distribution-spec/blob/main/spec.md 75 | #[derive(Clone, Debug)] 76 | pub struct CustomEncoding { 77 | symbol_table: Vec, 78 | reverse_table: BTreeMap, 79 | } 80 | 81 | impl Default for CustomEncoding { 82 | fn default() -> Self { 83 | Self::new() 84 | } 85 | } 86 | 87 | impl CustomEncoding { 88 | pub fn new() -> CustomEncoding { 89 | let mut symbol_table = Vec::new(); 90 | symbol_table.extend('0'..='9'); 91 | symbol_table.extend('a'..='z'); 92 | symbol_table.extend('A'..='Z'); 93 | symbol_table.push('-'); 94 | symbol_table.push('.'); 95 | 96 | let mut reverse_table = BTreeMap::new(); 97 | 98 | for (i, c) in symbol_table.iter().enumerate() { 99 | reverse_table.insert(*c, i as u32); 100 | } 101 | 102 | CustomEncoding { 103 | symbol_table, 104 | reverse_table, 105 | } 106 | } 107 | 108 | pub fn encode(&self, key: &str) -> String { 109 | let mut result = String::new(); 110 | let mut first = true; 111 | for c in key.chars() { 112 | match c { 113 | 'a'..='z' | 'A'..='Z' | '0'..='9' => result.push(c), 114 | '-' | '.' => { 115 | if first { 116 | self.encode_char(&mut result, c); 117 | } else { 118 | result.push(c); 119 | } 120 | } 121 | _ => self.encode_char(&mut result, c), 122 | } 123 | first = false; 124 | } 125 | result 126 | } 127 | 128 | fn encode_char(&self, result: &mut String, c: char) { 129 | result.push('_'); 130 | 131 | let mut n: u32 = c.into(); 132 | 133 | let mut char_code = Vec::new(); 134 | let base = self.symbol_table.len() as u32; 135 | while n != 0 { 136 | let quotient = n / base; 137 | let remainder = n % base; 138 | 139 | char_code.push(self.symbol_table[remainder as usize]); 140 | 141 | n = quotient; 142 | } 143 | result.extend(char_code.iter().rev()); 144 | 145 | result.push('_'); 146 | } 147 | 148 | pub fn decode(&self, tag: &str) -> Result { 149 | let mut chars = tag.chars(); 150 | let mut result = String::new(); 151 | while let Some(c) = chars.next() { 152 | match c { 153 | 'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '.' => result.push(c), 154 | '_' => { 155 | let mut encoded_char = Vec::new(); 156 | loop { 157 | match chars.next() { 158 | Some('_') => break, 159 | Some(n) => encoded_char.push(n), 160 | None => return Err(Error::InvalidTag(tag.to_string())), 161 | } 162 | } 163 | result.push( 164 | self.decode_char(&encoded_char) 165 | .ok_or_else(|| Error::InvalidTag(tag.to_string()))?, 166 | ) 167 | } 168 | _ => return Err(Error::InvalidTag(tag.to_string())), 169 | } 170 | } 171 | Ok(result) 172 | } 173 | 174 | fn decode_char(&self, encoded: &[char]) -> Option { 175 | let base = self.symbol_table.len() as u32; 176 | let mut n = 0u32; 177 | for c in encoded.iter() { 178 | n = n.checked_mul(base)?; 179 | n = n.checked_add(*self.reverse_table.get(c)?)?; 180 | } 181 | n.try_into().ok() 182 | } 183 | } 184 | 185 | #[cfg(test)] 186 | mod test { 187 | use super::*; 188 | 189 | #[test] 190 | fn custom_encode_symbol_table_length() { 191 | assert_eq!(CUSTOM_ENCODING.symbol_table.len(), 64); 192 | } 193 | 194 | #[test] 195 | fn custom_encode_symbol_table_validate() { 196 | assert_eq!( 197 | CUSTOM_ENCODING.symbol_table.len(), 198 | CUSTOM_ENCODING.reverse_table.len() 199 | ); 200 | for (i, c) in CUSTOM_ENCODING.symbol_table.iter().enumerate() { 201 | assert_eq!(CUSTOM_ENCODING.reverse_table[c], i as u32); 202 | } 203 | for (c, i) in CUSTOM_ENCODING.reverse_table.iter() { 204 | assert_eq!(CUSTOM_ENCODING.symbol_table[*i as usize], *c); 205 | } 206 | } 207 | 208 | #[test] 209 | fn custom_encode_id() { 210 | assert_eq!( 211 | CUSTOM_ENCODING 212 | .encode("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-."), 213 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-." 214 | ); 215 | } 216 | 217 | #[test] 218 | fn custom_encode_first_special() { 219 | assert_eq!(CUSTOM_ENCODING.encode("--"), "_J_-"); 220 | assert_eq!(CUSTOM_ENCODING.encode(".."), "_K_."); 221 | assert_eq!(CUSTOM_ENCODING.encode("//"), "_L__L_"); 222 | assert_eq!(CUSTOM_ENCODING.encode("__"), "_1v__1v_"); 223 | } 224 | 225 | #[test] 226 | fn custom_decode_id() { 227 | assert_eq!( 228 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-.", 229 | CUSTOM_ENCODING 230 | .decode("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-.") 231 | .unwrap() 232 | ); 233 | } 234 | 235 | #[test] 236 | fn custom_decode_first_special() { 237 | assert_eq!(("--"), CUSTOM_ENCODING.decode("_J_-").unwrap()); 238 | assert_eq!((".."), CUSTOM_ENCODING.decode("_K_.").unwrap()); 239 | assert_eq!(("//"), CUSTOM_ENCODING.decode("_L__L_").unwrap()); 240 | assert_eq!(("__"), CUSTOM_ENCODING.decode("_1v__1v_").unwrap()); 241 | } 242 | 243 | #[test] 244 | fn custom_encode_decode() { 245 | let test_strings = [ 246 | "test", 247 | "测试", 248 | "_test-测试_", 249 | "._test-测试_.", 250 | "._test-测试_.测试", 251 | "realisations/sha256:67890e0958e5d1a2944a3389151472a9acde025c7812f68381a7eef0d82152d1!libgcc.doi", 252 | ]; 253 | for s in test_strings { 254 | assert_eq!( 255 | CUSTOM_ENCODING.decode(&CUSTOM_ENCODING.encode(s)).unwrap(), 256 | s 257 | ); 258 | } 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::{env::VarError, ffi::OsString, path::PathBuf, string::FromUtf8Error}; 2 | 3 | use http::StatusCode; 4 | use oci_distribution::errors::OciDistributionError; 5 | use warp::reject::Reject; 6 | 7 | use crate::registry::OciLocation; 8 | 9 | #[derive(thiserror::Error, Debug)] 10 | pub enum Error { 11 | #[error("http error: {0}")] 12 | Http(#[from] http::Error), 13 | #[error("decode error: {0}")] 14 | Decode(#[from] data_encoding::DecodeError), 15 | #[error("tag-to-key error: {0:?}")] 16 | TagToKey(Vec), 17 | #[error("invalid tag error: {0}")] 18 | InvalidTag(String), 19 | #[error("from utf-8 error: {0}")] 20 | FromUtf8(#[from] FromUtf8Error), 21 | #[error("invalid authorization header: {0}")] 22 | InvalidAuthorization(String), 23 | #[error("oci distribution error: {0}")] 24 | OciDistribution(#[from] OciDistributionError), 25 | #[error("invalid image layer count: {0}")] 26 | InvalidLayerCount(usize), 27 | #[error("invalid image layer media type: {0}")] 28 | InvalidLayerMediaType(String), 29 | #[error("lack of layer annotations")] 30 | NoLayerAnnotations, 31 | #[error("lack of layer annotation key: {0}")] 32 | NoLayerAnnotationKey(String), 33 | #[error("reference not found: {0}")] 34 | ReferenceNotFound(OciLocation), 35 | #[error("invalid path: {0:?}")] 36 | InvalidPath(PathBuf), 37 | #[error("invalid os string: {0:?}")] 38 | InvalidOsString(OsString), 39 | #[error("reqwest error: {0:?}")] 40 | Reqwest(#[from] reqwest::Error), 41 | #[error("upstream error: {0:?}")] 42 | Upstream(reqwest::Response), 43 | #[error("io error: {0:?}")] 44 | Io(#[from] std::io::Error), 45 | #[error("rusqlite error: {0}")] 46 | Rusqlite(#[from] rusqlite::Error), 47 | #[error("duplicated path info: {0}")] 48 | DuplicatedPathInfo(String), 49 | #[error("no path info: {0}")] 50 | NoPathInfo(String), 51 | #[error("invalid store path: {0}")] 52 | InvalidStorePath(String), 53 | #[error("invalid signature: {0}")] 54 | InvalidSignature(String), 55 | #[error("invalid signing key: {0}")] 56 | InvalidSigningKey(String), 57 | #[error("early stop")] 58 | EarlyStop, 59 | #[error("tokio join error: {0}")] 60 | Join(#[from] tokio::task::JoinError), 61 | #[error("invalid nar size: {0}")] 62 | InvalidNarSize(>::Error), 63 | #[error("nar size not match: expected = {0}, actual = {1}")] 64 | NarSizeNotMatch(i64, usize), 65 | #[error("invalid max retry number: {0}")] 66 | InvalidMaxRetry(usize), 67 | #[error("retry all fails: {0:?}")] 68 | RetryAllFails(Vec), 69 | #[error("nix db folder '{0}' is not writable")] 70 | NixDbFolderNotWritable(String), 71 | #[error("push failed")] 72 | PushFailed, 73 | #[error("ed25519 error: {0}")] 74 | Ed25519(#[from] ed25519_compact::Error), 75 | #[error("unable to read environment variable `ORANC_SIGNING_KEY`: {0}")] 76 | InvalidSigningKeyEnv(VarError), 77 | #[error("signature mismatch for key '{name}': new = '{new}', exists = '{exists}'")] 78 | SignatureMismatch { 79 | name: String, 80 | new: String, 81 | exists: String, 82 | }, 83 | #[error("nar error: {0}")] 84 | Nar(#[from] nix_nar::NarError), 85 | } 86 | 87 | impl Error { 88 | pub fn code(&self) -> StatusCode { 89 | match self { 90 | Error::Http(_) => StatusCode::INTERNAL_SERVER_ERROR, 91 | Error::Decode(_) => StatusCode::BAD_REQUEST, 92 | Error::TagToKey(_) => StatusCode::BAD_REQUEST, 93 | Error::InvalidTag(_) => StatusCode::BAD_REQUEST, 94 | Error::FromUtf8(_) => StatusCode::BAD_REQUEST, 95 | Error::InvalidAuthorization(_) => StatusCode::BAD_REQUEST, 96 | Error::OciDistribution(_) => StatusCode::BAD_REQUEST, 97 | Error::InvalidLayerCount(_) => StatusCode::BAD_REQUEST, 98 | Error::InvalidLayerMediaType(_) => StatusCode::BAD_REQUEST, 99 | Error::NoLayerAnnotations => StatusCode::BAD_REQUEST, 100 | Error::NoLayerAnnotationKey(_) => StatusCode::BAD_REQUEST, 101 | Error::ReferenceNotFound(_) => StatusCode::NOT_FOUND, 102 | Error::InvalidPath(_) => StatusCode::BAD_REQUEST, 103 | Error::InvalidOsString(_) => StatusCode::BAD_REQUEST, 104 | Error::Reqwest(_) => StatusCode::INTERNAL_SERVER_ERROR, 105 | Error::Upstream(_) => StatusCode::INTERNAL_SERVER_ERROR, 106 | Error::Io(_) => StatusCode::INTERNAL_SERVER_ERROR, 107 | Error::Rusqlite(_) => StatusCode::INTERNAL_SERVER_ERROR, 108 | Error::DuplicatedPathInfo(_) => StatusCode::INTERNAL_SERVER_ERROR, 109 | Error::NoPathInfo(_) => StatusCode::BAD_REQUEST, 110 | Error::InvalidStorePath(_) => StatusCode::BAD_REQUEST, 111 | Error::InvalidSignature(_) => StatusCode::INTERNAL_SERVER_ERROR, 112 | Error::InvalidSigningKey(_) => StatusCode::BAD_REQUEST, 113 | Error::EarlyStop => StatusCode::INTERNAL_SERVER_ERROR, 114 | Error::Join(_) => StatusCode::INTERNAL_SERVER_ERROR, 115 | Error::InvalidNarSize(_) => StatusCode::INTERNAL_SERVER_ERROR, 116 | Error::NarSizeNotMatch(_, _) => StatusCode::INTERNAL_SERVER_ERROR, 117 | Error::InvalidMaxRetry(_) => StatusCode::BAD_REQUEST, 118 | Error::RetryAllFails(_) => StatusCode::INTERNAL_SERVER_ERROR, 119 | Error::NixDbFolderNotWritable(_) => StatusCode::BAD_REQUEST, 120 | Error::PushFailed => StatusCode::INTERNAL_SERVER_ERROR, 121 | Error::Ed25519(_) => StatusCode::BAD_REQUEST, 122 | Error::InvalidSigningKeyEnv(_) => StatusCode::BAD_REQUEST, 123 | Error::SignatureMismatch { .. } => StatusCode::BAD_REQUEST, 124 | Error::Nar(_) => StatusCode::INTERNAL_SERVER_ERROR, 125 | } 126 | } 127 | } 128 | 129 | impl Reject for Error {} 130 | -------------------------------------------------------------------------------- /src/key.rs: -------------------------------------------------------------------------------- 1 | use crate::{error::Error, options::TagCommands}; 2 | 3 | pub async fn key_main(command: TagCommands) -> Result<(), Error> { 4 | match command { 5 | TagCommands::Encode { 6 | key, 7 | fallbacks, 8 | encoding_options, 9 | } => { 10 | let (m, f) = encoding_options.key_to_tag(&key); 11 | println!("{}", m); 12 | if fallbacks { 13 | for tag in f { 14 | println!("{}", tag); 15 | } 16 | } 17 | } 18 | TagCommands::Decode { 19 | tag, 20 | encoding_options, 21 | } => println!("{}", encoding_options.tag_to_key(&tag)?), 22 | } 23 | Ok(()) 24 | } 25 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | pub mod convert; 2 | pub mod error; 3 | pub mod key; 4 | pub mod nix; 5 | pub mod options; 6 | pub mod push; 7 | pub mod registry; 8 | pub mod server; 9 | 10 | use clap::{CommandFactory, Parser}; 11 | 12 | use error::Error; 13 | use options::{Commands, Options}; 14 | use pretty_env_logger::formatted_builder; 15 | 16 | #[tokio::main] 17 | async fn main() { 18 | init_logger(); 19 | 20 | let options = options::Options::parse(); 21 | log::debug!("options = {:#?}", options); 22 | if let Err(e) = main_result(options).await { 23 | log::error!("{}", e); 24 | std::process::exit(1); 25 | } 26 | } 27 | 28 | fn init_logger() { 29 | let mut builder = formatted_builder(); 30 | let filters = match std::env::var("RUST_LOG") { 31 | Ok(f) => f, 32 | Err(_) => "oranc=info".to_string(), 33 | }; 34 | builder.parse_filters(&filters); 35 | builder.init() 36 | } 37 | 38 | async fn main_result(options: Options) -> Result<(), Error> { 39 | match options.command { 40 | Commands::Server(server_options) => server::server_main(server_options).await?, 41 | Commands::Tag(key_commands) => key::key_main(key_commands).await?, 42 | Commands::Push(push_options) => push::push_main(push_options).await?, 43 | Commands::Completion(completion_options) => { 44 | generate_shell_completions(completion_options).await? 45 | } 46 | } 47 | Ok(()) 48 | } 49 | 50 | async fn generate_shell_completions(gen_options: options::CompletionOptions) -> Result<(), Error> { 51 | let mut cli = options::Options::command(); 52 | let mut stdout = std::io::stdout(); 53 | clap_complete::generate(gen_options.shell, &mut cli, "oranc", &mut stdout); 54 | Ok(()) 55 | } 56 | -------------------------------------------------------------------------------- /src/nix.rs: -------------------------------------------------------------------------------- 1 | pub mod sign; 2 | 3 | use std::{ 4 | collections::{HashSet, VecDeque}, 5 | fmt, fs, 6 | path::PathBuf, 7 | }; 8 | 9 | use nix_base32::to_nix_base32; 10 | use once_cell::sync::Lazy; 11 | use regex::Regex; 12 | use sha2::{Digest, Sha256}; 13 | 14 | use crate::{error::Error, options::PushOptions}; 15 | 16 | use self::sign::{NixKeyPair, NixSignatureList}; 17 | 18 | static STORE_PATH_REGEX: Lazy = Lazy::new(|| Regex::new("^([a-z0-9]+)-(.*)$").unwrap()); 19 | 20 | #[derive(Debug, Clone)] 21 | pub struct NarInfo { 22 | pub store_path: String, 23 | pub url: String, 24 | pub compression: String, 25 | pub file_hash: NixHash, 26 | pub file_size: usize, 27 | pub nar_hash: NixHash, 28 | pub nar_size: usize, 29 | pub references: Vec, 30 | pub deriver: Option, 31 | pub sigs: NixSignatureList, 32 | pub ca: Option, 33 | } 34 | 35 | #[derive(Debug, Clone)] 36 | pub struct NixHash { 37 | pub algorithm: String, 38 | pub base32: String, 39 | } 40 | 41 | #[derive(Debug, Clone)] 42 | pub struct PathInfo { 43 | pub id: i64, 44 | pub path: String, 45 | pub deriver_store_paths: Option, 46 | pub nar_size: i64, 47 | pub sigs: Option, 48 | pub reference_store_paths: Vec, 49 | pub ca: Option, 50 | } 51 | 52 | pub fn canonicalize_store_path_input(store_dir: &str, input: &str) -> Result { 53 | let store_dir_prefix = format!("{store_dir}/"); 54 | let mut path = PathBuf::from(input); 55 | loop { 56 | if input.starts_with(&store_dir_prefix) { 57 | let path_os_str = path.as_os_str(); 58 | let path_str = path_os_str 59 | .to_str() 60 | .ok_or(Error::InvalidOsString(path_os_str.to_owned()))?; 61 | return Ok(path_str.to_owned()); 62 | } else { 63 | let metadata = fs::symlink_metadata(&path)?; 64 | if metadata.is_symlink() { 65 | path = fs::read_link(&path)? 66 | } else { 67 | return Err(Error::InvalidStorePath(input.to_owned())); 68 | } 69 | } 70 | } 71 | } 72 | 73 | pub fn query_db_ids( 74 | db: &rusqlite::Connection, 75 | paths: HashSet, 76 | ) -> Result, Error> { 77 | paths.into_iter().map(|p| query_path_id(db, &p)).collect() 78 | } 79 | 80 | pub fn compute_closure( 81 | db: &rusqlite::Connection, 82 | ids: HashSet, 83 | ) -> Result, Error> { 84 | let mut queue: VecDeque<_> = ids.into_iter().collect(); 85 | let mut result = HashSet::new(); 86 | while let Some(id) = queue.pop_front() { 87 | if result.contains(&id) { 88 | continue; 89 | } 90 | result.insert(id); 91 | let mut references = VecDeque::from(query_references(db, id)?); 92 | queue.append(&mut references); 93 | } 94 | Ok(result) 95 | } 96 | 97 | pub fn query_path_id(db: &rusqlite::Connection, path: &str) -> Result { 98 | let mut query_path = db.prepare_cached("SELECT id FROM ValidPaths WHERE path = ?")?; 99 | let mut query_result = query_path.query(rusqlite::params![path])?; 100 | let row = match query_result.next()? { 101 | Some(r) => r, 102 | None => return Err(Error::NoPathInfo(path.to_owned())), 103 | }; 104 | let id = row.get::<_, i64>(0)?; // sqlite integer 105 | if query_result.next()?.is_some() { 106 | return Err(Error::DuplicatedPathInfo(path.to_owned())); 107 | } 108 | Ok(id) 109 | } 110 | 111 | pub fn query_references(db: &rusqlite::Connection, id: i64) -> Result, Error> { 112 | let mut query_reference = db.prepare_cached("SELECT reference FROM Refs WHERE referrer = ?")?; 113 | let query_result = query_reference.query_map(rusqlite::params![id], |row| row.get(0))?; 114 | let mut result = vec![]; 115 | for r in query_result { 116 | result.push(r?); 117 | } 118 | Ok(result) 119 | } 120 | 121 | pub fn store_path_to_hash(options: &PushOptions, store_path: &str) -> Result { 122 | let stripped = strip_store_dir(options, store_path)?; 123 | let hash = match STORE_PATH_REGEX.captures(&stripped) { 124 | Some(captures) => captures[1].to_owned(), 125 | None => return Err(Error::InvalidStorePath(store_path.to_owned())), 126 | }; 127 | Ok(hash) 128 | } 129 | 130 | pub fn strip_store_dir(options: &PushOptions, store_path: &str) -> Result { 131 | let prefix = format!("{}/", options.store_dir); 132 | let stripped = match store_path.strip_prefix(&prefix) { 133 | None => return Err(Error::InvalidStorePath(store_path.to_owned())), 134 | Some(s) => s, 135 | }; 136 | Ok(stripped.to_owned()) 137 | } 138 | 139 | pub fn filter_id( 140 | options: &PushOptions, 141 | key_pair: &NixKeyPair, 142 | db: &rusqlite::Connection, 143 | id: i64, 144 | ) -> Result { 145 | let mut query_sigs = db.prepare_cached("SELECT sigs FROM ValidPaths WHERE id = ?")?; 146 | let sigs = 147 | query_sigs.query_row(rusqlite::params![id], |row| row.get::<_, Option>(0))?; 148 | filter_id_single(options, key_pair, &sigs) 149 | } 150 | 151 | pub fn filter_id_single( 152 | options: &PushOptions, 153 | key_pair: &NixKeyPair, 154 | sigs: &Option, 155 | ) -> Result { 156 | let sig_list = NixSignatureList::from_optional_str(sigs)?; 157 | for sig in sig_list.0 { 158 | if options.excluded_signing_key_pattern.is_match(&sig.name) { 159 | log::trace!("excluded path with signature name: {}", sig.name); 160 | return Ok(false); 161 | } 162 | if sig.name == key_pair.name && !options.already_signed { 163 | log::trace!( 164 | "excluded already signed path with signature name: {}", 165 | sig.name 166 | ); 167 | return Ok(false); 168 | } 169 | } 170 | Ok(true) 171 | } 172 | 173 | pub fn query_path_info(db: &rusqlite::Connection, id: i64) -> Result { 174 | let mut query_info = 175 | db.prepare_cached("SELECT path, deriver, narSize, sigs, ca FROM ValidPaths WHERE id = ?")?; 176 | let (path, deriver_store_paths, nar_size, sigs, ca) = 177 | query_info.query_row(rusqlite::params![id], |row| { 178 | Ok(( 179 | row.get(0)?, 180 | row.get(1)?, 181 | row.get(2)?, 182 | row.get(3)?, 183 | row.get(4)?, 184 | )) 185 | })?; 186 | let mut query_reference_paths = db.prepare_cached( 187 | "SELECT path FROM ValidPaths WHERE id IN (SELECT reference FROM Refs WHERE referrer = ?)", 188 | )?; 189 | let mut reference_store_paths: Vec<_> = query_reference_paths 190 | .query_map(rusqlite::params![id], |row| row.get::<_, String>(0))? 191 | .collect::>()?; 192 | reference_store_paths.sort(); 193 | Ok(PathInfo { 194 | id, 195 | path, 196 | deriver_store_paths, 197 | nar_size, 198 | sigs, 199 | reference_store_paths, 200 | ca, 201 | }) 202 | } 203 | 204 | impl NixHash { 205 | pub fn hash_data(data: &[u8]) -> NixHash { 206 | let sha256 = { 207 | let mut hasher = Sha256::new(); 208 | hasher.update(data); 209 | hasher.finalize() 210 | }; 211 | NixHash { 212 | algorithm: "sha256".to_string(), 213 | base32: to_nix_base32(&sha256[..]), 214 | } 215 | } 216 | } 217 | 218 | impl fmt::Display for NarInfo { 219 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 220 | writeln!(f, "StorePath: {}", self.store_path)?; 221 | writeln!(f, "URL: {}", self.url)?; 222 | writeln!(f, "Compression: {}", self.compression)?; 223 | writeln!(f, "FileHash: {}", self.file_hash)?; 224 | writeln!(f, "FileSize: {}", self.file_size)?; 225 | writeln!(f, "NarHash: {}", self.nar_hash)?; 226 | writeln!(f, "NarSize: {}", self.nar_size)?; 227 | writeln!(f, "References: {}", self.references.join(" "))?; 228 | if let Some(deriver) = &self.deriver { 229 | writeln!(f, "Deriver: {}", deriver)?; 230 | } 231 | for sig in &self.sigs.0 { 232 | writeln!(f, "Sig: {sig}")?; 233 | } 234 | if let Some(ca) = &self.ca { 235 | writeln!(f, "CA: {ca}")?; 236 | } 237 | Ok(()) 238 | } 239 | } 240 | 241 | // fingerprintPath: https://github.com/NixOS/nix/blob/master/perl/lib/Nix/Manifest.pm#L234 242 | pub fn nar_info_fingerprint( 243 | store_dir: &str, 244 | store_path: &str, 245 | nar_hash: &NixHash, 246 | nar_size: usize, 247 | references: &[String], 248 | ) -> String { 249 | let fingerprint = format!( 250 | "1;{store_path};{nar_hash};{nar_size};{comma_delimited_references}", 251 | comma_delimited_references = references 252 | .iter() 253 | .map(|r| format!("{store_dir}/{r}")) 254 | .collect::>() 255 | .join(",") 256 | ); 257 | log::trace!("fingerprint: {fingerprint}"); 258 | fingerprint 259 | } 260 | 261 | impl fmt::Display for NixHash { 262 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 263 | write!(f, "{}:{}", self.algorithm, self.base32) 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /src/nix/sign.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt, str::FromStr}; 2 | 3 | use data_encoding::BASE64; 4 | use ed25519_compact::{KeyPair, SecretKey, Signature}; 5 | use once_cell::sync::Lazy; 6 | use regex::Regex; 7 | 8 | use crate::error::Error; 9 | 10 | pub static SIG_REGEX: Lazy = Lazy::new(|| Regex::new("^([^:]+):(.*)$").unwrap()); 11 | 12 | #[derive(Debug, Clone)] 13 | pub struct NixKeyPair { 14 | pub name: String, 15 | pub key_pair: KeyPair, 16 | } 17 | 18 | #[derive(Debug, Clone)] 19 | pub struct NixSignature { 20 | pub name: String, 21 | pub signature: String, 22 | } 23 | 24 | #[derive(Debug, Clone)] 25 | pub struct NixSignatureList(pub Vec); 26 | 27 | impl NixKeyPair { 28 | pub fn from_secret_key_str(s: &str) -> Result { 29 | let c = SIG_REGEX 30 | .captures(s) 31 | .ok_or(Error::InvalidSigningKey(s.to_owned()))?; 32 | let name = c[1].to_owned(); 33 | let sk_bytes = BASE64.decode(c[2].as_bytes())?; 34 | let sk = SecretKey::from_slice(&sk_bytes)?; 35 | let pk = sk.public_key(); 36 | Ok(NixKeyPair { 37 | name, 38 | key_pair: KeyPair { sk, pk }, 39 | }) 40 | } 41 | 42 | pub fn sign(&self, data: &[u8]) -> Result { 43 | let sign = self.key_pair.sk.sign(data, None); 44 | let encoded_sign = BASE64.encode(sign.as_ref()); 45 | Ok(NixSignature { 46 | name: self.name.clone(), 47 | signature: encoded_sign, 48 | }) 49 | } 50 | 51 | pub fn verify(&self, data: &[u8], signature: &NixSignature) -> Result<(), Error> { 52 | Ok(self.key_pair.pk.verify(data, &signature.signature()?)?) 53 | } 54 | } 55 | 56 | impl FromStr for NixSignature { 57 | type Err = Error; 58 | 59 | fn from_str(s: &str) -> Result { 60 | let c = SIG_REGEX 61 | .captures(s) 62 | .ok_or(Error::InvalidSignature(s.to_owned()))?; 63 | Ok(Self { 64 | name: c[1].to_owned(), 65 | signature: c[2].to_owned(), 66 | }) 67 | } 68 | } 69 | 70 | impl NixSignature { 71 | fn signature(&self) -> Result { 72 | Ok(Signature::from_slice( 73 | &BASE64.decode(self.signature.as_bytes())?, 74 | )?) 75 | } 76 | } 77 | 78 | impl fmt::Display for NixSignature { 79 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 80 | write!(f, "{}:{}", self.name, self.signature) 81 | } 82 | } 83 | 84 | impl NixSignatureList { 85 | pub fn from_optional_str(o: &Option) -> Result { 86 | match o { 87 | None => Ok(Self(Vec::new())), 88 | Some(s) => { 89 | let s = s 90 | .split(' ') 91 | .map(NixSignature::from_str) 92 | .collect::>()?; 93 | Ok(Self(s)) 94 | } 95 | } 96 | } 97 | } 98 | 99 | impl NixSignatureList { 100 | pub fn merge( 101 | &mut self, 102 | key_pair: &NixKeyPair, 103 | data: &[u8], 104 | new: NixSignature, 105 | ) -> Result<(), Error> { 106 | assert!(key_pair.name == new.name); 107 | 108 | let mut already_exists = false; 109 | for s in self.0.iter() { 110 | if s.name == new.name { 111 | key_pair.verify(data, s)?; 112 | already_exists = true; 113 | if s.signature != new.signature { 114 | return Err(Error::SignatureMismatch { 115 | name: key_pair.name.clone(), 116 | new: new.signature, 117 | exists: s.signature.clone(), 118 | }); 119 | } 120 | } 121 | } 122 | if !already_exists { 123 | self.0.push(new) 124 | } 125 | Ok(()) 126 | } 127 | } 128 | 129 | impl fmt::Display for NixSignatureList { 130 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 131 | let mut first = true; 132 | for s in &self.0 { 133 | if !first { 134 | write!(f, " ")?; 135 | } 136 | first = false; 137 | write!(f, "{}", s)?; 138 | } 139 | Ok(()) 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/options.rs: -------------------------------------------------------------------------------- 1 | use clap::{Parser, Subcommand}; 2 | use regex::Regex; 3 | use reqwest::Url; 4 | 5 | use std::net::SocketAddr; 6 | 7 | use crate::convert::EncodingOptions; 8 | 9 | #[derive(Clone, Debug, Parser)] 10 | #[command(author, version, about, long_about = None)] 11 | pub struct Options { 12 | #[command(subcommand)] 13 | pub command: Commands, 14 | } 15 | 16 | #[derive(Clone, Debug, Subcommand)] 17 | pub enum Commands { 18 | Server(ServerOptions), 19 | #[command(subcommand)] 20 | Tag(TagCommands), 21 | Push(PushOptions), 22 | Completion(CompletionOptions), 23 | } 24 | 25 | #[derive(Clone, Debug, Parser)] 26 | #[command(about = "HTTP Nix cache server backed by OCI Registry")] 27 | pub struct ServerOptions { 28 | #[arg(short, long, default_value = "[::]:8080")] 29 | pub listen: SocketAddr, 30 | #[arg(short, long, value_name = "NUM", default_value = "3")] 31 | pub max_retry: usize, 32 | #[arg(long, help = "disable ssl")] 33 | pub no_ssl: bool, 34 | #[arg(short, long, value_name = "URL", help = "upstream cache URLs")] 35 | pub upstream: Vec, 36 | #[arg( 37 | short, 38 | long, 39 | value_name = "PATTERN", 40 | default_value = "nix-cache-info", 41 | help = "ignored file matched when querying upstream" 42 | )] 43 | pub ignore_upstream: Regex, 44 | #[arg(long, help = "upstream anonymous queries")] 45 | pub upstream_anonymous: bool, 46 | #[clap(flatten)] 47 | pub encoding_options: EncodingOptions, 48 | } 49 | 50 | #[derive(Clone, Debug, Subcommand)] 51 | #[command(about = "Command line tools for tag-key conversion")] 52 | pub enum TagCommands { 53 | #[command(about = "Encode a key to tag")] 54 | Encode { 55 | key: String, 56 | #[arg(long)] 57 | fallbacks: bool, 58 | #[clap(flatten)] 59 | encoding_options: EncodingOptions, 60 | }, 61 | #[command(about = "Decode a tag to key")] 62 | Decode { 63 | tag: String, 64 | #[clap(flatten)] 65 | encoding_options: EncodingOptions, 66 | }, 67 | } 68 | 69 | #[derive(Clone, Debug, Parser)] 70 | #[command(about = "Push store paths to OCI Registry")] 71 | pub struct PushOptions { 72 | #[arg(long, default_value = "ghcr.io")] 73 | pub registry: String, 74 | #[arg(long)] 75 | pub repository: String, 76 | #[arg(long, help = "do not compute closure for input paths")] 77 | pub no_closure: bool, 78 | #[arg(long, default_value = "/nix/store")] 79 | pub store_dir: String, 80 | #[arg( 81 | short, 82 | long, 83 | value_name = "REGEX", 84 | default_value = "^cache\\.nixos\\.org-.*$" 85 | )] 86 | pub excluded_signing_key_pattern: Regex, 87 | #[arg(long, help = "push paths already signed by signing key")] 88 | pub already_signed: bool, 89 | #[arg(short, long, value_name = "NUM", default_value = "4")] 90 | pub parallel: usize, 91 | #[arg(short, long, value_name = "NUM", default_value = "3")] 92 | pub zstd_level: i32, 93 | #[arg(short, long, value_name = "NUM", default_value = "3")] 94 | pub max_retry: usize, 95 | #[arg(long)] 96 | pub dry_run: bool, 97 | #[arg(long, help = "allow open nix store sqlite database in immutable mode")] 98 | pub allow_immutable_db: bool, 99 | #[arg(long, help = "disable ssl")] 100 | pub no_ssl: bool, 101 | #[clap(flatten)] 102 | pub encoding_options: EncodingOptions, 103 | #[command(subcommand)] 104 | pub subcommand: Option, 105 | } 106 | 107 | #[derive(Clone, Debug, Subcommand)] 108 | pub enum PushSubcommands { 109 | Initialize(InitializeOptions), 110 | } 111 | 112 | #[derive(Clone, Debug, Parser)] 113 | #[command(about = "Initialize nix-cache-info")] 114 | pub struct InitializeOptions { 115 | #[arg(short, long, default_value = "41")] 116 | pub priority: u32, 117 | #[arg(short, long)] 118 | pub no_mass_query: bool, 119 | } 120 | 121 | #[derive(Clone, Debug, Parser)] 122 | #[command(about = "Generate shell completions")] 123 | pub struct CompletionOptions { 124 | pub shell: clap_complete::Shell, 125 | } 126 | -------------------------------------------------------------------------------- /src/push.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self}; 2 | 3 | use std::sync::atomic::AtomicUsize; 4 | use std::{ 5 | collections::HashSet, 6 | env, 7 | sync::atomic::{AtomicBool, Ordering}, 8 | }; 9 | 10 | use futures::StreamExt; 11 | 12 | use oci_distribution::secrets::RegistryAuth; 13 | use once_cell::sync::Lazy; 14 | use tempfile::tempdir_in; 15 | 16 | const NIX_DB_DIR: &str = "/nix/var/nix/db"; 17 | static NIX_DB_FILE: Lazy = Lazy::new(|| format!("{}/db.sqlite", NIX_DB_DIR)); 18 | const NAR_CONTENT_TYPE: &str = "application/x-nix-nar"; 19 | const NARINFO_CONTENT_TYPE: &str = "text/x-nix-narinfo"; 20 | 21 | use crate::nix::sign::{NixKeyPair, NixSignatureList}; 22 | use crate::nix::{NarInfo, NixHash}; 23 | use crate::registry::{OciItem, OciLocation, RegistryOptions}; 24 | use crate::{ 25 | error::Error, 26 | nix, 27 | options::{InitializeOptions, PushOptions, PushSubcommands}, 28 | registry, 29 | }; 30 | 31 | fn nix_db_connection(options: &PushOptions) -> Result { 32 | match tempdir_in(NIX_DB_DIR) { 33 | Ok(probe_dir) => { 34 | // NIX_DB_DIR is writable 35 | probe_dir.close()?; 36 | let db_uri = format!("file:{}?mode=ro", *NIX_DB_FILE); 37 | Ok(rusqlite::Connection::open(db_uri)?) 38 | } 39 | Err(_) => { 40 | if options.allow_immutable_db { 41 | log::warn!("open nix store database in immutable mode"); 42 | let db_uri = format!("file:{}?immutable=1", *NIX_DB_FILE); 43 | Ok(rusqlite::Connection::open(db_uri)?) 44 | } else { 45 | Err(Error::NixDbFolderNotWritable(NIX_DB_DIR.to_string())) 46 | } 47 | } 48 | } 49 | } 50 | 51 | fn get_auth() -> RegistryAuth { 52 | let username = match env::var("ORANC_USERNAME") { 53 | Ok(u) => u, 54 | Err(e) => { 55 | log::info!( 56 | "use anonymous auth, invalid environment variable `ORANC_USERNAME`: {}", 57 | e 58 | ); 59 | return RegistryAuth::Anonymous; 60 | } 61 | }; 62 | let password = match env::var("ORANC_PASSWORD") { 63 | Ok(u) => u, 64 | Err(e) => { 65 | log::info!( 66 | "use anonymous auth, invalid environment variable `ORANC_PASSWORD`: {}", 67 | e 68 | ); 69 | return RegistryAuth::Anonymous; 70 | } 71 | }; 72 | RegistryAuth::Basic(username, password) 73 | } 74 | 75 | fn get_nix_key_pair() -> Result { 76 | let sk_str = env::var("ORANC_SIGNING_KEY").map_err(Error::InvalidSigningKeyEnv)?; 77 | NixKeyPair::from_secret_key_str(&sk_str) 78 | } 79 | 80 | fn build_nix_cache_info(options: &PushOptions, initialize_options: &InitializeOptions) -> String { 81 | format!( 82 | "StoreDir: {store_dir} 83 | WantMassQuery: {mass_query} 84 | Priority: {priority} 85 | ", 86 | store_dir = options.store_dir, 87 | mass_query = if initialize_options.no_mass_query { 88 | 0 89 | } else { 90 | 1 91 | }, 92 | priority = initialize_options.priority 93 | ) 94 | } 95 | 96 | async fn push_one( 97 | options: &PushOptions, 98 | auth: &RegistryAuth, 99 | key_pair: &NixKeyPair, 100 | id: i64, 101 | failed: &AtomicBool, 102 | task_counter: &AtomicUsize, 103 | total_tasks: usize, 104 | ) -> Result<(), Error> { 105 | let not_failed = || { 106 | if failed.load(Ordering::Relaxed) { 107 | Err(Error::EarlyStop) 108 | } else { 109 | Ok(()) 110 | } 111 | }; 112 | let task_num = task_counter.fetch_add(1, Ordering::Relaxed); 113 | let task_header = format!( 114 | "{task_num:>width$}/{total_tasks}", 115 | width = total_tasks.to_string().len() 116 | ); 117 | 118 | not_failed()?; 119 | 120 | let to_put = tokio::task::spawn_blocking({ 121 | let options = options.clone(); 122 | let key_pair = key_pair.clone(); 123 | move || { 124 | // this function runs in parallel and use its own connections 125 | let conn = nix_db_connection(&options)?; 126 | 127 | let path_info = nix::query_path_info(&conn, id)?; 128 | let store_path = path_info.path.clone(); 129 | let store_path_hash = nix::store_path_to_hash(&options, &store_path)?; 130 | 131 | log::info!("[{task_header}] pushing '{}'...", store_path); 132 | 133 | let nar_info_filename = format!("{store_path_hash}.narinfo"); 134 | let mut nar_data = vec![]; 135 | let mut nar_encoder = nix_nar::Encoder::new(&store_path)?; 136 | io::copy(&mut nar_encoder, &mut nar_data)?; 137 | let nar_size = nar_data.len(); 138 | let nar_hash = NixHash::hash_data(&nar_data[..]); 139 | let mut nar_file_data = vec![]; 140 | zstd::stream::copy_encode(&nar_data[..], &mut nar_file_data, options.zstd_level)?; 141 | drop(nar_data); // save some memory 142 | let nar_file_size = nar_file_data.len(); 143 | let nar_file_hash = NixHash::hash_data(&nar_file_data[..]); 144 | 145 | let expected_nar_size: usize = path_info 146 | .nar_size 147 | .try_into() 148 | .map_err(Error::InvalidNarSize)?; 149 | if nar_size != expected_nar_size { 150 | return Err(Error::NarSizeNotMatch(path_info.nar_size, nar_size)); 151 | } 152 | 153 | let nar_filename = format!("{}.nar.zst", nar_hash.base32); 154 | let nar_file_url = format!("nar/{nar_filename}"); 155 | let references: Vec = path_info 156 | .reference_store_paths 157 | .iter() 158 | .map(|p| nix::strip_store_dir(&options, p)) 159 | .collect::>()?; 160 | let deriver: Option = path_info 161 | .deriver_store_paths 162 | .map_or(Ok::<_, Error>(None), |p| { 163 | Ok(Some(nix::strip_store_dir(&options, &p)?)) 164 | })?; 165 | let nar_info_fingerprint = nix::nar_info_fingerprint( 166 | &options.store_dir, 167 | &store_path, 168 | &nar_hash, 169 | nar_size, 170 | &references, 171 | ); 172 | let nar_info_sign = key_pair.sign(nar_info_fingerprint.as_bytes())?; 173 | let mut sig_list = NixSignatureList::from_optional_str(&path_info.sigs)?; 174 | sig_list.merge(&key_pair, nar_info_fingerprint.as_bytes(), nar_info_sign)?; 175 | let nar_info = NarInfo { 176 | store_path, 177 | url: nar_file_url.clone(), 178 | compression: "zstd".to_owned(), 179 | file_hash: nar_file_hash, 180 | file_size: nar_file_size, 181 | nar_hash, 182 | nar_size, 183 | references, 184 | deriver, 185 | sigs: sig_list, 186 | ca: path_info.ca, 187 | }; 188 | let nar_info_content = nar_info.to_string(); 189 | log::debug!("[{task_header}] narinfo:\n{nar_info_content}"); 190 | let nar_info_data = nar_info_content.into_bytes(); 191 | 192 | Ok(vec![ 193 | // push nar first 194 | ( 195 | OciLocation { 196 | registry: options.registry.clone(), 197 | repository: options.repository.clone(), 198 | key: nar_file_url, 199 | }, 200 | OciItem { 201 | content_type: Some(NAR_CONTENT_TYPE.to_owned()), 202 | data: nar_file_data, 203 | }, 204 | ), 205 | ( 206 | OciLocation { 207 | registry: options.registry, 208 | repository: options.repository, 209 | key: nar_info_filename, 210 | }, 211 | OciItem { 212 | content_type: Some(NARINFO_CONTENT_TYPE.to_owned()), 213 | data: nar_info_data, 214 | }, 215 | ), 216 | ]) 217 | } 218 | }) 219 | .await??; 220 | 221 | let mut ctx = RegistryOptions::from_push_options(options).context(auth.clone()); 222 | for (location, item) in to_put { 223 | registry::put(&mut ctx, &location, item).await?; 224 | } 225 | Ok(()) 226 | } 227 | 228 | async fn handle_push_result(r: Result<(), Error>, failed: &AtomicBool) { 229 | if let Err(e) = r { 230 | match e { 231 | Error::EarlyStop => { 232 | // do nothing 233 | } 234 | e => { 235 | log::error!("{}", e); // auto locked 236 | failed.store(true, Ordering::Relaxed); 237 | } 238 | } 239 | } 240 | } 241 | 242 | pub async fn push_main(options: PushOptions) -> Result<(), Error> { 243 | let auth = get_auth(); 244 | if let Some(cmd) = &options.subcommand { 245 | match cmd { 246 | PushSubcommands::Initialize(initialize_options) => { 247 | push_initialize_main(auth, options.clone(), initialize_options.clone()).await? 248 | } 249 | } 250 | return Ok(()); 251 | } 252 | // real main for push 253 | push(&auth, &options).await?; 254 | Ok(()) 255 | } 256 | 257 | pub async fn push(auth: &RegistryAuth, options: &PushOptions) -> Result<(), Error> { 258 | let conn = nix_db_connection(options)?; 259 | 260 | let lines = std::io::stdin().lines(); 261 | let mut input_paths = HashSet::new(); 262 | for result in lines { 263 | let line = result?; 264 | let trimmed = line.trim(); 265 | if !trimmed.is_empty() { 266 | input_paths.insert(trimmed.to_string()); 267 | } 268 | } 269 | log::info!("number of input paths: {}", input_paths.len()); 270 | log::trace!("input paths: {:#?}", input_paths); 271 | let canonical_paths: HashSet<_> = input_paths 272 | .into_iter() 273 | .map(|p| nix::canonicalize_store_path_input(&options.store_dir, &p)) 274 | .collect::>()?; 275 | log::info!("number of input store paths: {}", canonical_paths.len()); 276 | log::trace!("canonical paths: {:#?}", canonical_paths); 277 | let id_closures = if options.no_closure { 278 | nix::query_db_ids(&conn, canonical_paths)? 279 | } else { 280 | let ids = nix::query_db_ids(&conn, canonical_paths)?; 281 | nix::compute_closure(&conn, ids)? 282 | }; 283 | log::info!("number of store paths in closure: {}", id_closures.len()); 284 | log::trace!("id closure: {:#?}", id_closures); 285 | let key_pair = get_nix_key_pair()?; 286 | let mut filtered = HashSet::new(); 287 | for id in id_closures { 288 | if nix::filter_id(options, &key_pair, &conn, id)? { 289 | filtered.insert(id); 290 | } 291 | } 292 | let task_counter = AtomicUsize::new(1); 293 | let total_tasks = filtered.len(); 294 | let failed = AtomicBool::new(false); 295 | log::info!("number of store paths after filtering: {}", filtered.len()); 296 | log::trace!("filtered: {:#?}", filtered); 297 | log::info!("start {total_tasks} tasks..."); 298 | let pushes = futures::stream::iter(filtered.into_iter().map(|id| { 299 | push_one( 300 | options, 301 | auth, 302 | &key_pair, 303 | id, 304 | &failed, 305 | &task_counter, 306 | total_tasks, 307 | ) 308 | })) 309 | .buffer_unordered(options.parallel); 310 | pushes.for_each(|r| handle_push_result(r, &failed)).await; 311 | if failed.load(Ordering::Relaxed) { 312 | Err(Error::PushFailed) 313 | } else { 314 | log::info!("done."); 315 | Ok(()) 316 | } 317 | } 318 | 319 | pub async fn push_initialize_main( 320 | auth: RegistryAuth, 321 | options: PushOptions, 322 | initialize_options: InitializeOptions, 323 | ) -> Result<(), Error> { 324 | let nix_cache_info = build_nix_cache_info(&options, &initialize_options); 325 | log::debug!("nix-cache-info:\n{nix_cache_info}"); 326 | let key = "nix-cache-info".to_owned(); 327 | let content_type = "text/x-nix-cache-info".to_owned(); 328 | let mut ctx = RegistryOptions::from_push_options(&options).context(auth); 329 | let location = OciLocation { 330 | registry: options.registry, 331 | repository: options.repository, 332 | key, 333 | }; 334 | let item = OciItem { 335 | content_type: Some(content_type), 336 | data: nix_cache_info.into_bytes(), 337 | }; 338 | registry::put(&mut ctx, &location, item).await?; 339 | Ok(()) 340 | } 341 | -------------------------------------------------------------------------------- /src/registry.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use crate::convert::EncodingOptions; 4 | use crate::{ 5 | error::Error, 6 | options::{PushOptions, ServerOptions}, 7 | }; 8 | use maplit::hashmap; 9 | use oci_distribution::{ 10 | Client, Reference, 11 | client::{ClientConfig, ClientProtocol, Config, ImageLayer}, 12 | config::{Architecture, ConfigFile, Os, Rootfs}, 13 | errors::{OciDistributionError, OciErrorCode}, 14 | manifest::OciImageManifest, 15 | secrets::RegistryAuth, 16 | }; 17 | 18 | pub const LAYER_MEDIA_TYPE: &str = "application/octet-stream"; 19 | pub const CONTENT_TYPE_ANNOTATION: &str = "com.linyinfeng.oranc.content.type"; 20 | 21 | pub struct RegistryContext { 22 | pub options: RegistryOptions, 23 | pub client: Client, 24 | pub auth: RegistryAuth, 25 | } 26 | 27 | #[derive(Debug, Clone)] 28 | pub struct RegistryOptions { 29 | pub no_ssl: bool, 30 | pub dry_run: bool, 31 | pub max_retry: usize, 32 | pub encoding_options: EncodingOptions, 33 | } 34 | 35 | #[derive(Debug, Clone)] 36 | pub struct OciLocation { 37 | pub registry: String, 38 | pub repository: String, 39 | pub key: String, 40 | } 41 | 42 | #[derive(Debug, Clone)] 43 | pub struct OciItem { 44 | pub content_type: Option, 45 | pub data: Vec, 46 | } 47 | 48 | #[derive(Debug, Clone)] 49 | pub struct LayerInfo { 50 | pub reference: Reference, 51 | pub digest: String, 52 | pub content_type: String, 53 | } 54 | 55 | impl OciLocation { 56 | pub fn reference(&self, encoding_options: &EncodingOptions) -> (Reference, Vec) { 57 | let build_ref = 58 | |tag| Reference::with_tag(self.registry.clone(), self.repository.clone(), tag); 59 | let (main_tag, fallback_tags) = encoding_options.key_to_tag(&self.key); 60 | let main = build_ref(main_tag); 61 | let fallback_refs = fallback_tags.into_iter().map(build_ref).collect(); 62 | (main, fallback_refs) 63 | } 64 | 65 | pub fn references_merged(&self, encoding_options: &EncodingOptions) -> Vec { 66 | let (main, fallbacks) = self.reference(encoding_options); 67 | let mut result = vec![main]; 68 | result.extend(fallbacks); 69 | result 70 | } 71 | } 72 | 73 | pub async fn get_layer_info( 74 | ctx: &mut RegistryContext, 75 | location: &OciLocation, 76 | ) -> Result, Error> { 77 | let max_retry = ctx.options.max_retry; 78 | if max_retry < 1 { 79 | return Err(Error::InvalidMaxRetry(max_retry)); 80 | } 81 | 82 | let references = location.references_merged(&ctx.options.encoding_options); 83 | let mut pull_result = None; 84 | let mut errors = vec![]; 85 | 'fallbacks: for reference in references { 86 | let mut ref_errors = vec![]; 87 | 'retries: for attempt in 1..max_retry { 88 | log::debug!("pull image manifest {reference:?}, attempt {attempt}/{max_retry}"); 89 | match ctx.client.pull_image_manifest(&reference, &ctx.auth).await { 90 | Ok(res) => { 91 | pull_result = Some((reference.clone(), res)); 92 | break 'fallbacks; 93 | } 94 | Err(OciDistributionError::ImageManifestNotFoundError(_)) => break 'retries, 95 | Err(OciDistributionError::RegistryError { envelope, .. }) 96 | if envelope 97 | .errors 98 | .iter() 99 | .all(|e| e.code == OciErrorCode::ManifestUnknown) => 100 | { 101 | break 'retries; 102 | } 103 | Err(oci_error) => { 104 | let e = oci_error.into(); 105 | log::warn!( 106 | "pull image manifest {reference:?}, attempt {attempt}/{max_retry} failed: {}", 107 | e 108 | ); 109 | ref_errors.push(e); 110 | } 111 | } 112 | } 113 | if ref_errors.len() == max_retry { 114 | log::error!("pull image manifest {reference:?} failed"); 115 | // all reties failed 116 | errors.extend(ref_errors); 117 | } 118 | } 119 | let (reference, (manifest, _hash)) = match pull_result { 120 | Some(r) => r, 121 | None => { 122 | if errors.is_empty() { 123 | // all reference not found 124 | return Ok(None); 125 | } else { 126 | // at least one reference failed 127 | return Err(Error::RetryAllFails(errors)); 128 | } 129 | } 130 | }; 131 | 132 | match manifest.layers.len() { 133 | 1 => (), 134 | other => return Err(Error::InvalidLayerCount(other)), 135 | } 136 | let layer_manifest = &manifest.layers[0]; 137 | if layer_manifest.media_type != LAYER_MEDIA_TYPE { 138 | return Err(Error::InvalidLayerMediaType( 139 | layer_manifest.media_type.clone(), 140 | )); 141 | } 142 | let annotations = match &layer_manifest.annotations { 143 | Some(a) => a, 144 | None => return Err(Error::NoLayerAnnotations), 145 | }; 146 | let content_type = match annotations.get(CONTENT_TYPE_ANNOTATION) { 147 | Some(a) => a, 148 | None => { 149 | return Err(Error::NoLayerAnnotationKey( 150 | CONTENT_TYPE_ANNOTATION.to_string(), 151 | )); 152 | } 153 | }; 154 | let info = LayerInfo { 155 | reference, 156 | digest: layer_manifest.digest.clone(), 157 | content_type: content_type.clone(), 158 | }; 159 | Ok(Some(info)) 160 | } 161 | 162 | pub async fn put( 163 | ctx: &mut RegistryContext, 164 | location: &OciLocation, 165 | oci_item: OciItem, 166 | ) -> Result<(), Error> { 167 | let content_type = match oci_item.content_type { 168 | None => "application/octet-stream".to_string(), 169 | Some(c) => c, 170 | }; 171 | let layer_annotations = hashmap! { 172 | CONTENT_TYPE_ANNOTATION.to_string() => content_type, 173 | }; 174 | let layer = ImageLayer::new( 175 | oci_item.data, 176 | LAYER_MEDIA_TYPE.to_string(), 177 | Some(layer_annotations), 178 | ); 179 | let layer_digest = layer.sha256_digest(); 180 | let layers = vec![layer]; 181 | 182 | let rootfs = Rootfs { 183 | r#type: "layers".to_string(), 184 | diff_ids: vec![ 185 | // just use layer digest 186 | layer_digest, 187 | ], 188 | }; 189 | let config_file = ConfigFile { 190 | created: None, 191 | author: None, 192 | architecture: Architecture::None, 193 | os: Os::None, 194 | config: None, 195 | rootfs, 196 | history: None, 197 | }; 198 | let config_annotations = None; 199 | let config = Config::oci_v1_from_config_file(config_file, config_annotations) 200 | .map_err(Error::OciDistribution)?; 201 | 202 | let key = &location.key; 203 | let image_annotations = hashmap! { 204 | "com.linyinfeng.oranc.key".to_string() => key.to_owned(), 205 | "org.opencontainers.image.description".to_string() => key.to_owned(), 206 | }; 207 | let image_manifest = OciImageManifest::build(&layers, &config, Some(image_annotations)); 208 | 209 | let max_retry = ctx.options.max_retry; 210 | if max_retry < 1 { 211 | return Err(Error::InvalidMaxRetry(max_retry)); 212 | } 213 | let (reference, _fallbacks) = location.reference(&ctx.options.encoding_options); 214 | let mut errors = vec![]; 215 | for attempt in 1..max_retry { 216 | log::debug!("push {reference:?}, attempt {attempt}/{max_retry}"); 217 | if ctx.options.dry_run { 218 | log::debug!("dry run, skipped"); 219 | return Ok(()); 220 | } 221 | match ctx 222 | .client 223 | .push( 224 | &reference, 225 | &layers, 226 | config.clone(), 227 | &ctx.auth, 228 | Some(image_manifest.clone()), 229 | ) 230 | .await 231 | { 232 | Ok(_push_response) => return Ok(()), 233 | Err(oci_error) => { 234 | let e = oci_error.into(); 235 | log::warn!( 236 | "push {reference:?}, attempt {attempt}/{max_retry} failed: {}", 237 | e 238 | ); 239 | errors.push(e); 240 | } 241 | } 242 | } 243 | Err(Error::RetryAllFails(errors)) 244 | } 245 | 246 | impl RegistryOptions { 247 | pub fn from_push_options(options: &PushOptions) -> Self { 248 | Self { 249 | dry_run: options.dry_run, 250 | max_retry: options.max_retry, 251 | no_ssl: options.no_ssl, 252 | encoding_options: options.encoding_options.clone(), 253 | } 254 | } 255 | 256 | pub fn from_server_options(options: &ServerOptions) -> Self { 257 | Self { 258 | dry_run: false, 259 | max_retry: options.max_retry, 260 | no_ssl: options.no_ssl, 261 | encoding_options: options.encoding_options.clone(), 262 | } 263 | } 264 | 265 | pub fn client_config(&self) -> ClientConfig { 266 | let protocol = if self.no_ssl { 267 | ClientProtocol::Http 268 | } else { 269 | ClientProtocol::Https 270 | }; 271 | ClientConfig { 272 | protocol, 273 | ..Default::default() 274 | } 275 | } 276 | 277 | pub fn client(&self) -> Client { 278 | Client::new(self.client_config()) 279 | } 280 | 281 | pub fn context(self, auth: RegistryAuth) -> RegistryContext { 282 | let client = self.client(); 283 | RegistryContext { 284 | options: self, 285 | client, 286 | auth, 287 | } 288 | } 289 | } 290 | 291 | impl fmt::Display for OciLocation { 292 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 293 | write!(f, "{}/{}/{}", self.registry, self.repository, self.key) 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /src/server.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use crate::registry; 3 | use crate::registry::LayerInfo; 4 | use crate::registry::OciItem; 5 | use crate::registry::OciLocation; 6 | use crate::registry::RegistryOptions; 7 | use crate::registry::get_layer_info; 8 | 9 | pub mod upstream; 10 | 11 | use bytes::Bytes; 12 | use data_encoding::BASE64; 13 | use http::Response; 14 | use http::StatusCode; 15 | use http::header; 16 | use hyper::Body; 17 | 18 | use oci_distribution::secrets::RegistryAuth; 19 | use once_cell::sync::Lazy; 20 | use regex::Regex; 21 | use warp::{Filter, Rejection, Reply}; 22 | 23 | use crate::options::ServerOptions; 24 | 25 | const OK_RESPONSE_BODY: &str = "<_/>"; 26 | const NO_SUCH_KEY_RESPONSE_BODY: &str = "NoSuchKey"; 27 | 28 | static AWS_AUTH_PATTERN: Lazy = 29 | Lazy::new(|| Regex::new("^AWS4-HMAC-SHA256 Credential=([^ /,]+)/.*$").unwrap()); 30 | static BASIC_AUTH_PATTERN: Lazy = Lazy::new(|| Regex::new("^Basic (.*)$").unwrap()); 31 | static DECODED_PATTERN: Lazy = Lazy::new(|| Regex::new("^([^:]+):(.+)$").unwrap()); 32 | 33 | #[derive(Debug, Clone)] 34 | pub struct ServerContext { 35 | options: ServerOptions, 36 | http_client: reqwest::Client, 37 | } 38 | 39 | pub async fn get( 40 | ctx: ServerContext, 41 | location: OciLocation, 42 | auth: RegistryAuth, 43 | ) -> Result, Rejection> { 44 | log::info!("get: {location}"); 45 | if let Some(response) = upstream::check_and_redirect(&ctx, &location.key, &auth).await? { 46 | return Ok(response); 47 | } 48 | let mut registry_ctx = RegistryOptions::from_server_options(&ctx.options).context(auth); 49 | let LayerInfo { 50 | reference, 51 | digest, 52 | content_type, 53 | } = get_layer_info(&mut registry_ctx, &location) 54 | .await? 55 | .ok_or(Error::ReferenceNotFound(location.clone()))?; 56 | let blob_stream = registry_ctx 57 | .client 58 | .pull_blob_stream(&reference, &digest) 59 | .await 60 | .map_err(Error::OciDistribution)?; 61 | Ok(Response::builder() 62 | .status(StatusCode::OK) 63 | .header(header::CONTENT_TYPE, content_type) 64 | .body(Body::wrap_stream(blob_stream)) 65 | .map_err(Error::Http)?) 66 | } 67 | 68 | pub async fn head( 69 | ctx: ServerContext, 70 | location: OciLocation, 71 | auth: RegistryAuth, 72 | ) -> Result, Rejection> { 73 | log::info!("head: {location}"); 74 | if let Some(response) = upstream::check_and_redirect(&ctx, &location.key, &auth).await? { 75 | return Ok(response); 76 | } 77 | let mut registry_ctx = RegistryOptions::from_server_options(&ctx.options).context(auth); 78 | let LayerInfo { 79 | reference: _, 80 | digest: _, 81 | content_type, 82 | } = get_layer_info(&mut registry_ctx, &location) 83 | .await? 84 | .ok_or(Error::ReferenceNotFound(location.clone()))?; 85 | Ok(Response::builder() 86 | .status(StatusCode::OK) 87 | .header(header::CONTENT_TYPE, content_type) 88 | .body(Body::empty()) 89 | .map_err(Error::Http)?) 90 | } 91 | 92 | pub async fn put( 93 | ctx: ServerContext, 94 | location: OciLocation, 95 | auth: RegistryAuth, 96 | content_type: Option, 97 | body: Bytes, 98 | ) -> Result, Rejection> { 99 | log::info!("put: {location}"); 100 | // on upstream query for put 101 | let mut registry_ctx = RegistryOptions::from_server_options(&ctx.options).context(auth); 102 | let item = OciItem { 103 | content_type, 104 | data: body.to_vec(), 105 | }; 106 | registry::put(&mut registry_ctx, &location, item).await?; 107 | Ok(Response::builder() 108 | .status(StatusCode::OK) 109 | .body(OK_RESPONSE_BODY) 110 | .map_err(Error::Http)?) 111 | } 112 | 113 | pub fn registry_auth() -> impl Filter + Copy { 114 | warp::header::optional("authorization").and_then(parse_auth) 115 | } 116 | 117 | pub async fn parse_auth(opt: Option) -> Result { 118 | match opt { 119 | None => Ok(RegistryAuth::Anonymous), 120 | Some(original) => { 121 | let captures = (BASIC_AUTH_PATTERN.captures(&original)) 122 | .or_else(|| AWS_AUTH_PATTERN.captures(&original)); 123 | let encoded = match &captures { 124 | Some(c) => c[1].as_bytes(), 125 | None => return Err(Error::InvalidAuthorization(original).into()), 126 | }; 127 | let bytes = BASE64.decode(encoded).map_err(Error::Decode)?; 128 | let decoded = String::from_utf8(bytes).map_err(Error::FromUtf8)?; 129 | match DECODED_PATTERN.captures(&decoded) { 130 | Some(captures) => Ok(RegistryAuth::Basic( 131 | captures[1].to_string(), 132 | captures[2].to_string(), 133 | )), 134 | None => Err(Error::InvalidAuthorization(original).into()), 135 | } 136 | } 137 | } 138 | } 139 | 140 | pub fn oci_location() -> impl Filter + Copy { 141 | warp::path::param() // registry 142 | .and(warp::path::param()) // repository part1 143 | .and(warp::path::param()) // repository part1 144 | .and(warp::path::tail()) // key 145 | .and_then(convert_to_oci_location) 146 | } 147 | 148 | pub async fn convert_to_oci_location( 149 | registry: String, 150 | rep1: String, 151 | rep2: String, 152 | tail: warp::path::Tail, 153 | ) -> Result { 154 | let tail_str = tail.as_str(); 155 | let decoded_registry = urlencoding::decode(®istry).map_err(Error::FromUtf8)?; 156 | let decoded_rep1 = urlencoding::decode(&rep1).map_err(Error::FromUtf8)?; 157 | let decoded_rep2 = urlencoding::decode(&rep2).map_err(Error::FromUtf8)?; 158 | let decoded_tail = urlencoding::decode(tail_str).map_err(Error::FromUtf8)?; 159 | let repository = format!("{decoded_rep1}/{decoded_rep2}"); 160 | Ok(OciLocation { 161 | registry: decoded_registry.to_string(), 162 | repository, 163 | key: decoded_tail.to_string(), 164 | }) 165 | } 166 | 167 | pub async fn handle_error(rejection: Rejection) -> Result { 168 | log::trace!("handle rejection: {rejection:?}"); 169 | let code; 170 | let message; 171 | if let Some(e) = rejection.find::() { 172 | log::debug!("handle error: {e}"); 173 | code = e.code(); 174 | match e { 175 | // otherwise aws clients can not decode 404 error message 176 | Error::ReferenceNotFound(_) => message = NO_SUCH_KEY_RESPONSE_BODY.to_string(), 177 | _ => message = format!("error: {}\n", e), 178 | } 179 | } else if rejection.is_not_found() { 180 | code = StatusCode::NOT_FOUND; 181 | message = "not found".to_string(); 182 | } else { 183 | return Err(rejection); 184 | } 185 | Ok(warp::reply::with_status(message, code)) 186 | } 187 | 188 | pub async fn log_rejection(rejection: Rejection) -> Result, Rejection> { 189 | log::debug!("unhandled rejection: {rejection:?}"); 190 | Err(rejection) 191 | } 192 | 193 | pub async fn server_main(options: ServerOptions) -> Result<(), Error> { 194 | let http_client = reqwest::Client::new(); 195 | let ctx = ServerContext { 196 | options, 197 | http_client, 198 | }; 199 | 200 | let ctx_filter = { 201 | let ctx = ctx.clone(); 202 | warp::any().map(move || ctx.clone()) 203 | }; 204 | let common = || ctx_filter.clone().and(oci_location()).and(registry_auth()); 205 | let main = warp::get() 206 | .and(warp::path::end()) 207 | .map(|| "oranc: OCI Registry As Nix Cache") 208 | .or(warp::get() 209 | .and(common()) 210 | .and_then(get) 211 | .recover(handle_error)) 212 | .or(warp::head() 213 | .and(common()) 214 | .and_then(head) 215 | .recover(handle_error)) 216 | .or(warp::put() 217 | .and(common()) 218 | .and(warp::header::optional("content-type")) 219 | .and(warp::body::bytes()) 220 | .and_then(put) 221 | .recover(handle_error)); 222 | 223 | let log = warp::log::custom(|info| { 224 | log::trace!( 225 | "from {remote_addr:?} {elapsed:?} 226 | {version:?} {method} {host:?} {path} {status} 227 | {request_headers:?}", 228 | remote_addr = info.remote_addr(), 229 | elapsed = info.elapsed(), 230 | version = info.version(), 231 | method = info.method(), 232 | host = info.host(), 233 | path = info.path(), 234 | status = info.status(), 235 | request_headers = info.request_headers(), 236 | ) 237 | }); 238 | 239 | let routes = main.recover(log_rejection).with(log); 240 | 241 | warp::serve(routes).run(ctx.options.listen).await; 242 | Ok(()) 243 | } 244 | -------------------------------------------------------------------------------- /src/server/upstream.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use http::{Response, StatusCode}; 4 | use hyper::Body; 5 | use oci_distribution::secrets::RegistryAuth; 6 | use reqwest::Url; 7 | use warp::Rejection; 8 | 9 | use crate::error::Error; 10 | 11 | use super::ServerContext; 12 | 13 | pub async fn check_and_redirect( 14 | ctx: &ServerContext, 15 | key: &str, 16 | auth: &RegistryAuth, 17 | ) -> Result>, Rejection> { 18 | match check(ctx, key, auth).await? { 19 | Some(url) => Ok(Some(redirect_response(key, &url)?)), 20 | None => Ok(None), 21 | } 22 | } 23 | 24 | pub async fn check( 25 | ctx: &ServerContext, 26 | key: &str, 27 | auth: &RegistryAuth, 28 | ) -> Result, Error> { 29 | let max_retry = ctx.options.max_retry; 30 | if max_retry < 1 { 31 | return Err(Error::InvalidMaxRetry(max_retry)); 32 | } 33 | 34 | if let RegistryAuth::Anonymous = auth { 35 | // skip check upstream caches if `--upstream-anonymous` is off 36 | if !ctx.options.upstream_anonymous { 37 | log::debug!("skipped checking upstream for key: '{}'", key); 38 | return Ok(None); 39 | } 40 | } 41 | if ctx.options.ignore_upstream.is_match(key) { 42 | return Ok(None); 43 | } 44 | for upstream in &ctx.options.upstream { 45 | let url = upstream_url(upstream, key)?; 46 | for attempt in 1..max_retry { 47 | let response = ctx 48 | .http_client 49 | .head(url.clone()) 50 | .send() 51 | .await 52 | .map_err(Error::Reqwest)?; 53 | if response.status() == StatusCode::OK { 54 | return Ok(Some(url)); 55 | } else if response.status() == StatusCode::NOT_FOUND { 56 | break; 57 | } else { 58 | log::warn!( 59 | "query upstream url '{url}', attempt {attempt}/{max_retry} failed: {:?}", 60 | response 61 | ); 62 | } 63 | } 64 | } 65 | Ok(None) 66 | } 67 | 68 | pub fn upstream_url(base: &Url, key: &str) -> Result { 69 | let path = base.path(); 70 | let new_path = PathBuf::from(path).join(key); 71 | match new_path.to_str() { 72 | Some(p) => { 73 | let mut upstream = base.clone(); 74 | upstream.set_path(p); 75 | Ok(upstream) 76 | } 77 | None => Err(Error::InvalidPath(new_path)), 78 | } 79 | } 80 | 81 | pub fn redirect_response(key: &str, url: &Url) -> Result, Error> { 82 | log::info!("redirect: key = {key}, url = {url}"); 83 | Response::builder() 84 | .status(StatusCode::FOUND) 85 | .header(http::header::LOCATION, url.to_string()) 86 | .body(Body::empty()) 87 | .map_err(Error::Http) 88 | } 89 | --------------------------------------------------------------------------------