Mirrors for pypi.org, npm, crates.io, Rust, Zig and their compilers
Make pip, uv, npm, cargo, rustup and zig download through Zaryn when pypi.org, npmjs.com, crates.io, GitHub or ziglang.org are slow or unreachable. Every file is checked against a hash your tools already know, so the mirror can't change anything.
A **mirror** is a copy of another site's files kept closer to you. When pypi.org, npmjs.com, crates.io, GitHub or ziglang.org are slow, blocked, or down, pip, uv, npm, cargo, rustup and zig can download through Zaryn instead.
## Why you can trust a mirror
Normally a mirror is a risk: whoever runs it could hand you a changed file. These mirrors are built so that can't happen:
1. Before Zaryn keeps a file, it checks it against a hash that comes from the original source: the sha256 in pypi.org's index for Python packages, the integrity in npm's package document for npm packages, the checksum in crates.io's index for crates, the hash written in your `build.zig.zon` for Zig packages.
2. When the file reaches you, cargo or zig checks the same hash again, on your computer. A file that doesn't match is refused.
So the worst a mirror could do is fail to answer; it can never give you different code. (Installing Rust itself with rustup works a little differently; [its section](#rust-itself-rustup) says how.) A file Zaryn has fetched once also keeps working when the original site is down.
## All at once: zev mirror
If you have the `zev` command ([install it](/docs/en/installation)), one line sets up pip, uv, npm, cargo and rustup:
```bash
zev mirror on
```
`zev mirror` shows what is set up, `zev mirror on pip cargo` picks some, and `zev mirror off` takes it all out again. zev only adds a clearly marked block to each tool's own settings file and never changes a setting you made yourself: if, say, your `~/.npmrc` already points at another registry, zev leaves it and tells you. For a Zig project, `zev mirror zig` (in the project's folder) points its `build.zig.zon` at the mirror and fetches what its dependencies need; `zev mirror zig --undo` puts the addresses back.
The sections below explain each tool by hand.
## Python: pip and uv
Zaryn's Python index holds your own projects on Zaryn and everything on pypi.org:
```bash
pip config set global.index-url https://api.zaryn.xyz/v1/packages/pypi/all/simple/
# uv
export UV_DEFAULT_INDEX=https://api.zaryn.xyz/v1/packages/pypi/all/simple/
# and Python itself, for uv python install
export UV_PYTHON_INSTALL_MIRROR=https://api.zaryn.xyz/v1/packages/python/-/standalone
```
Every file from pypi.org is checked against the sha256 in pypi.org's index; Python builds against their release's SHA256SUMS. A name that exists on Zaryn always comes from Zaryn, so a look-alike published on pypi.org can't slip in. More in [Python packages](/docs/en/packages-pypi).
## JavaScript: npm
```bash
npm config set registry https://api.zaryn.xyz/v1/packages/npm-mirror/
```
pnpm, yarn and bun read the same setting. Every tarball is checked against the sha512 *integrity* npm's package document lists, the same value npm checks after downloading. Packages you publish on Zaryn keep their own scope line in `.npmrc` (see [npm packages](/docs/en/packages-npm)); the mirror is for everything else.
## Rust crates
Add this to `~/.cargo/config.toml` (the settings file cargo reads every time it runs; create it if it doesn't exist):
```toml
[source.crates-io]
replace-with = "zaryn"
[source.zaryn]
registry = "sparse+https://api.zaryn.xyz/v1/packages/cargo/mirror/index/"
```
- `[source.crates-io] replace-with = "zaryn"` tells cargo: whenever you'd go to crates.io, go to the source called `zaryn` instead.
- `[source.zaryn]` says where that is.
That's all. `cargo build`, `cargo add`, `cargo update` and `cargo install` now get crates.io crates through Zaryn, and nothing else in your projects changes: `Cargo.toml` and `Cargo.lock` stay the same, and your teammates without the setting still download from crates.io.
What happens behind the scenes:
- cargo asks Zaryn for a crate's index file (the list of its versions and their checksums). Zaryn answers from its copy if it's less than 5 minutes old, otherwise asks crates.io. If crates.io doesn't answer, you get the copy Zaryn has.
- cargo asks for a `.crate` file. Zaryn sends its copy, or downloads it from crates.io, compares its SHA-256 with the checksum in the index, and only then keeps and sends it.
To stop using the mirror, delete those lines.
## Rust itself (rustup)
rustup, the tool that installs and updates Rust, can download through Zaryn too. Set two variables before running it:
```bash
export RUSTUP_DIST_SERVER=https://api.zaryn.xyz/v1/packages/rust/-
export RUSTUP_UPDATE_ROOT=https://api.zaryn.xyz/v1/packages/rust/-/rustup
```
- `RUSTUP_DIST_SERVER` is where Rust itself comes from (the compiler, cargo, the standard library).
- `RUSTUP_UPDATE_ROOT` is where rustup gets new versions of itself.
Put the two lines in `~/.bashrc` (or your shell's start file) so they stay set. Then use rustup as usual:
```bash
# first install, with the install script through Zaryn too
curl -sSf https://api.zaryn.xyz/v1/packages/rust/-/rustup/rustup-init.sh | sh
# later
rustup update
rustup target add wasm32-unknown-unknown
```
Before Zaryn keeps a file, it checks it against the `.sha256` file static.rust-lang.org publishes next to it, and rustup checks every download against the checksums in the channel file. The channel file itself comes to you from Zaryn, which fetched it from static.rust-lang.org over HTTPS; this is the one place you rely on Zaryn, as with any Rust mirror. When static.rust-lang.org can't be reached, the last channel Zaryn has keeps working.
## Zig dependencies
Zig has no mirror setting: each dependency in `build.zig.zon` has its own address. So instead, the addresses are changed to go through Zaryn, while the hashes stay exactly as they are (and zig keeps checking them).
**On the web:** open [Packages](/packages), scroll to **Mirrors**, paste your `build.zig.zon` and press **Point at Zaryn**, or **Point at Zaryn and check everything** to have Zaryn download each dependency now and report any problem. Copy or download the result and replace your file.
**From a terminal** (`jq` picks the file out of the answer):
```bash
curl --data-binary @build.zig.zon \
"https://api.zaryn.xyz/v1/packages/zig/-/mirror-zon?warm=1" | jq -r .zon > build.zig.zon.new
mv build.zig.zon.new build.zig.zon
```
A dependency like
```zig
.known_folders = .{
.url = "https://github.com/ziglibs/known-folders/archive/207c34a16e4365edc20d92c7892f962b3bed46e8.tar.gz",
.hash = "known_folders-0.0.0-Fy-PJsbKAACbDh9bBxR0MMThxZSS6A9RH4apWphNHY70",
},
```
becomes
```zig
.known_folders = .{
.url = "https://api.zaryn.xyz/v1/packages/zig/-/mirror/known_folders-0.0.0-Fy-PJsbKAACbDh9bBxR0MMThxZSS6A9RH4apWphNHY70/github.com/ziglibs/known-folders/archive/207c34a16e4365edc20d92c7892f962b3bed46e8.tar.gz",
.hash = "known_folders-0.0.0-Fy-PJsbKAACbDh9bBxR0MMThxZSS6A9RH4apWphNHY70",
},
```
The new address holds the hash and the original address, so you can also write it by hand: `https://api.zaryn.xyz/v1/packages/zig/-/mirror/<hash>/<original address without https://>`. Zaryn downloads the original once, checks that its Zig hash is the one in the address, and keeps it. If the file at the original address has changed, Zaryn refuses it, exactly as zig would.
To go back to the original addresses, use **Undo** on the web, or `?undo=1` instead of `?warm=1`.
### Dependencies of your dependencies
Your dependencies have their own `build.zig.zon` files with their own addresses, which you can't change. zig only downloads a package it doesn't already have, though, so download those through Zaryn once and zig won't need the original site. **Point at Zaryn and check everything** lists them and gives a ready set of commands, one per package:
```bash
zig fetch https://api.zaryn.xyz/v1/packages/zig/-/mirror/<hash>/<address>
```
### What can be mirrored
- Archives over `https://` from GitHub, Codeberg, GitLab, SourceHut, Bitbucket, ziglang.org and a few other well-known code hosts (a server's admin can add more). Other addresses are left as they are.
- `.tar.gz`, `.tar.xz`, `.tar.zst`, `.tar` and `.zip` archives.
- `git+https://` dependencies with a `#ref` (zig fetch --save writes the commit there). Zaryn checks out that commit the way zig does and serves it as a `.tar.gz`; the hash is the same, because zig hashes the files, not the archive. The mirror address looks like `…/mirror/<hash>/git/github.com/owner/repo?ref=<commit>`.
- Dependencies that already have a `.hash`. A new one gets its hash the first time you run `zig build` or `zig fetch --save`; mirror after that.
Any package Zaryn already has can also be fetched by its hash alone: `https://api.zaryn.xyz/v1/packages/zig/-/hash/<hash>`.
## Zig toolchains
The Zig compiler itself, from ziglang.org, by its file name:
```bash
curl -LO https://api.zaryn.xyz/v1/packages/zig/-/toolchain/zig-x86_64-linux-0.15.1.tar.xz
curl -LO https://api.zaryn.xyz/v1/packages/zig/-/toolchain/zig-x86_64-linux-0.15.1.tar.xz.minisig
```
- Use the exact file names from [ziglang.org/download](https://ziglang.org/download/): releases (`zig-<cpu>-<system>-<version>.tar.xz`, `.zip` on Windows) and dev builds (`…-0.16.0-dev.…`).
- Dev builds stay available on Zaryn after ziglang.org removes them, so a project pinned to one keeps building.
- `https://api.zaryn.xyz/v1/packages/zig/-/toolchain/index.json` is ziglang.org's list of releases.
Check the download with the Zig Software Foundation's key, exactly as you would from ziglang.org:
```bash
minisign -Vm zig-x86_64-linux-0.15.1.tar.xz -P RWSGOq2NVecA2UPNdBUZykf1CCb147pkmdtYxgb3Ti+JO/wCYvhbAb/U
```
Tools that support Zig's community mirrors can use `https://api.zaryn.xyz/v1/packages/zig/-/toolchain` as one.
## How much the mirrors hold
`https://api.zaryn.xyz/v1/packages/-/mirrors` shows what each mirror holds and how many downloads it answered in the last 30 days. When the cache is full, the files nobody has asked for in the longest time make room first; they're downloaded again if someone needs them.
A mirror is a copy of another site's files kept closer to you. When pypi.org, npmjs.com, crates.io, GitHub or ziglang.org are slow, blocked, or down, pip, uv, npm, cargo, rustup and zig can download through Zaryn instead.
Normally a mirror is a risk: whoever runs it could hand you a changed file. These mirrors are built so that can't happen:
Before Zaryn keeps a file, it checks it against a hash that comes from the original source: the sha256 in pypi.org's index for Python packages, the integrity in npm's package document for npm packages, the checksum in crates.io's index for crates, the hash written in your build.zig.zon for Zig packages.
When the file reaches you, cargo or zig checks the same hash again, on your computer. A file that doesn't match is refused.
So the worst a mirror could do is fail to answer; it can never give you different code. (Installing Rust itself with rustup works a little differently; its section says how.) A file Zaryn has fetched once also keeps working when the original site is down.
If you have the zev command (install it), one line sets up pip, uv, npm, cargo and rustup:
Terminal
zev mirror on
zev mirror shows what is set up, zev mirror on pip cargo picks some, and zev mirror off takes it all out again. zev only adds a clearly marked block to each tool's own settings file and never changes a setting you made yourself: if, say, your ~/.npmrc already points at another registry, zev leaves it and tells you. For a Zig project, zev mirror zig (in the project's folder) points its build.zig.zon at the mirror and fetches what its dependencies need; zev mirror zig --undo puts the addresses back.
Zaryn's Python index holds your own projects on Zaryn and everything on pypi.org:
Terminal
pip config set global.index-url https://api.zaryn.xyz/v1/packages/pypi/all/simple/# uvexport UV_DEFAULT_INDEX=https://api.zaryn.xyz/v1/packages/pypi/all/simple/# and Python itself, for uv python installexport UV_PYTHON_INSTALL_MIRROR=https://api.zaryn.xyz/v1/packages/python/-/standalone
Every file from pypi.org is checked against the sha256 in pypi.org's index; Python builds against their release's SHA256SUMS. A name that exists on Zaryn always comes from Zaryn, so a look-alike published on pypi.org can't slip in. More in Python packages.
npm config set registry https://api.zaryn.xyz/v1/packages/npm-mirror/
pnpm, yarn and bun read the same setting. Every tarball is checked against the sha512 integrity npm's package document lists, the same value npm checks after downloading. Packages you publish on Zaryn keep their own scope line in .npmrc (see npm packages); the mirror is for everything else.
[source.crates-io] replace-with = "zaryn" tells cargo: whenever you'd go to crates.io, go to the source called zaryn instead.
[source.zaryn] says where that is.
That's all. cargo build, cargo add, cargo update and cargo install now get crates.io crates through Zaryn, and nothing else in your projects changes: Cargo.toml and Cargo.lock stay the same, and your teammates without the setting still download from crates.io.
What happens behind the scenes:
cargo asks Zaryn for a crate's index file (the list of its versions and their checksums). Zaryn answers from its copy if it's less than 5 minutes old, otherwise asks crates.io. If crates.io doesn't answer, you get the copy Zaryn has.
cargo asks for a .crate file. Zaryn sends its copy, or downloads it from crates.io, compares its SHA-256 with the checksum in the index, and only then keeps and sends it.
RUSTUP_DIST_SERVER is where Rust itself comes from (the compiler, cargo, the standard library).
RUSTUP_UPDATE_ROOT is where rustup gets new versions of itself.
Put the two lines in ~/.bashrc (or your shell's start file) so they stay set. Then use rustup as usual:
Terminal
# first install, with the install script through Zaryn toocurl -sSf https://api.zaryn.xyz/v1/packages/rust/-/rustup/rustup-init.sh | sh# laterrustup updaterustup target add wasm32-unknown-unknown
Before Zaryn keeps a file, it checks it against the .sha256 file static.rust-lang.org publishes next to it, and rustup checks every download against the checksums in the channel file. The channel file itself comes to you from Zaryn, which fetched it from static.rust-lang.org over HTTPS; this is the one place you rely on Zaryn, as with any Rust mirror. When static.rust-lang.org can't be reached, the last channel Zaryn has keeps working.
Zig has no mirror setting: each dependency in build.zig.zon has its own address. So instead, the addresses are changed to go through Zaryn, while the hashes stay exactly as they are (and zig keeps checking them).
On the web: open Packages, scroll to Mirrors, paste your build.zig.zon and press Point at Zaryn, or Point at Zaryn and check everything to have Zaryn download each dependency now and report any problem. Copy or download the result and replace your file.
From a terminal (jq picks the file out of the answer):
The new address holds the hash and the original address, so you can also write it by hand: https://api.zaryn.xyz/v1/packages/zig/-/mirror/<hash>/<original address without https://>. Zaryn downloads the original once, checks that its Zig hash is the one in the address, and keeps it. If the file at the original address has changed, Zaryn refuses it, exactly as zig would.
To go back to the original addresses, use Undo on the web, or ?undo=1 instead of ?warm=1.
Your dependencies have their own build.zig.zon files with their own addresses, which you can't change. zig only downloads a package it doesn't already have, though, so download those through Zaryn once and zig won't need the original site. Point at Zaryn and check everything lists them and gives a ready set of commands, one per package:
Archives over https:// from GitHub, Codeberg, GitLab, SourceHut, Bitbucket, ziglang.org and a few other well-known code hosts (a server's admin can add more). Other addresses are left as they are.
.tar.gz, .tar.xz, .tar.zst, .tar and .zip archives.
git+https:// dependencies with a #ref (zig fetch --save writes the commit there). Zaryn checks out that commit the way zig does and serves it as a .tar.gz; the hash is the same, because zig hashes the files, not the archive. The mirror address looks like …/mirror/<hash>/git/github.com/owner/repo?ref=<commit>.
Dependencies that already have a .hash. A new one gets its hash the first time you run zig build or zig fetch --save; mirror after that.
Any package Zaryn already has can also be fetched by its hash alone: https://api.zaryn.xyz/v1/packages/zig/-/hash/<hash>.
https://api.zaryn.xyz/v1/packages/-/mirrors shows what each mirror holds and how many downloads it answered in the last 30 days. When the cache is full, the files nobody has asked for in the longest time make room first; they're downloaded again if someone needs them.