[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-93490":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":15,"subscribersCount":15,"size":15,"stars1d":16,"stars7d":17,"stars30d":17,"stars90d":15,"forks30d":15,"starsTrendScore":18,"compositeScore":19,"rankGlobal":10,"rankLanguage":10,"license":20,"archived":21,"fork":21,"defaultBranch":22,"hasWiki":23,"hasPages":21,"topics":24,"createdAt":10,"pushedAt":10,"updatedAt":25,"readmeContent":26,"aiSummary":10,"trendingCount":15,"starSnapshotCount":15,"syncStatus":27,"lastSyncTime":28,"discoverSource":29},93490,"MirrorProxy","inbjo\u002FMirrorProxy","inbjo","All-in-one mirror acceleration","https:\u002F\u002Fsina.dev",null,"Rust",190,12,104,0,5,24,34,67.74,"MIT License",false,"main",true,[],"2026-07-22 04:02:09","# MirrorProxy\n\n[English](README.md) | [简体中文](README_CN.md)\n\n[![CI](https:\u002F\u002Fimg.shields.io\u002Fgithub\u002Factions\u002Fworkflow\u002Fstatus\u002Finbjo\u002FMirrorProxy\u002Fci.yml?branch=main&style=flat-square&logo=githubactions&logoColor=white&label=CI)](https:\u002F\u002Fgithub.com\u002Finbjo\u002FMirrorProxy\u002Factions\u002Fworkflows\u002Fci.yml)\n[![Release](https:\u002F\u002Fimg.shields.io\u002Fgithub\u002Fv\u002Frelease\u002Finbjo\u002FMirrorProxy?style=flat-square&logo=github&label=Release)](https:\u002F\u002Fgithub.com\u002Finbjo\u002FMirrorProxy\u002Freleases\u002Flatest)\n[![License](https:\u002F\u002Fimg.shields.io\u002Fgithub\u002Flicense\u002Finbjo\u002FMirrorProxy?style=flat-square&label=License)](LICENSE)\n[![Docker Pulls](https:\u002F\u002Fimg.shields.io\u002Fdocker\u002Fpulls\u002Fkudang\u002Fmirrorproxy?style=flat-square&logo=docker&logoColor=white&label=Docker%20Pulls)](https:\u002F\u002Fhub.docker.com\u002Fr\u002Fkudang\u002Fmirrorproxy)\n[![Clients](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Fclients-Windows%20%7C%20macOS%20%7C%20Linux-2f81f7?style=flat-square)](#install-the-client)\n[![GitHub Stars](https:\u002F\u002Fimg.shields.io\u002Fgithub\u002Fstars\u002Finbjo\u002FMirrorProxy?style=flat-square&logo=github&label=Stars)](https:\u002F\u002Fgithub.com\u002Finbjo\u002FMirrorProxy\u002Fstargazers)\n\nMirrorProxy is a self-hosted mirror proxy platform written in Rust. The `mirrorproxy-server` service and the `mirrorproxy` source-management client are independent binaries. The server embeds the React + Vite + Tailwind web console; the standalone client runs on Windows, macOS, and Linux.\n\nThe project uses an adapter-based proxy core and already ships adapters for GitHub, Docker\u002FOCI, Composer, npm, PyPI, Cargo, Go modules, major language repositories, developer-tool distribution services, and operating system mirrors. New ecosystems can reuse the same routing, streaming, security, accounting, quota, and cache infrastructure.\n\n## Features\n\n- Embedded public and authenticated administration web console at `\u002F`\n- Health endpoint at `\u002Fhealthz`\n- Runtime public config endpoint at `\u002Fapi\u002Fconfig`\n- Source catalog endpoint at `\u002Fapi\u002Fsources`\n- SQLite-backed settings, administrator sessions, audit log, traffic accounting, monthly quota, rate limiting, and bounded disk cache\n- Standalone Windows\u002FmacOS\u002FLinux client with source editing, exact rollback, and GitHub HTTPS Git URL rewriting\n- GitHub proxy for repository pages, raw files, release assets, archives, and Composer GitHub dist URLs\n- Composer proxy at `\u002Fcomposer`\n- Docker\u002FOCI proxy at `\u002Fv2\u002F*` for Docker Hub, GHCR, Quay, and Kubernetes public images\n- npm\u002Fyarn\u002Fpnpm proxy at `\u002Fnpm`\n- Node.js distribution proxy for NVM at `\u002Fnvm`\n- opam repository proxy at `\u002Fopam`\n- Go module proxy at `\u002Fgoproxy`\n- Maven Central proxy at `\u002Fmaven`\n- RubyGems proxy at `\u002Frubygems`\n- Rustup distribution proxy at `\u002Frustup`\n- NuGet v3 proxy at `\u002Fnuget\u002Fv3\u002Findex.json`\n- CPAN repository proxy at `\u002Fcpan`\n- CRAN repository proxy at `\u002Fcran`\n- Hackage repository proxy at `\u002Fhackage`\n- Julia package server proxy at `\u002Fjulia`\n- LuaRocks repository proxy at `\u002Fluarocks`\n- Clojars repository proxy at `\u002Fclojars`\n- CocoaPods CDN proxy at `\u002Fcocoapods`\n- Dart \u002F Flutter Pub proxy at `\u002Fpub`\n- Anaconda \u002F Conda proxy at `\u002Fanaconda`\n- TeX Live proxy at `\u002Ftexlive`\n- WinGet source proxy at `\u002Fwinget`\n- GNU ELPA proxy at `\u002Felpa`\n- Nix binary cache proxy at `\u002Fnix`\n- GNU Guix substitute cache proxy at `\u002Fguix`\n- Flatpak OSTree proxy at `\u002Fflatpak`\n- Homebrew bottle proxy at `\u002Fhomebrew`\n- Allowlisted Linux, BSD, MSYS2, OpenWrt, Termux, ROS, and related operating-system repositories under `\u002Fos`\n- Cargo sparse registry proxy at `\u002Fcrates-index`\n- pip\u002FPyPI proxy at `\u002Fpypi\u002Fsimple`\n- Streamed upstream responses with hop-by-hop header filtering\n- Safe defaults that reject unsupported absolute proxy targets\n\n## Quick Start\n\n```bash\ncargo run -p mirrorproxy-server --bin mirrorproxy-server -- --config config.example.toml\n```\n\nOpen:\n\n```text\nhttp:\u002F\u002Fselfhost.com\n```\n\nCheck health:\n\n```bash\ncurl http:\u002F\u002Fselfhost.com\u002Fhealthz\n```\n\n## Docker Deployment\n\nThe server image runs as non-root UID\u002FGID `10001`, listens on port `3000`, and\nstores its SQLite database and optional cache under the `\u002Fdata` volume.\n\nThe repository includes a ready-to-run [compose.yaml](compose.yaml). You can\nalso save the following as `docker-compose.yaml` in an empty deployment\ndirectory:\n\n```yaml\nservices:\n  mirrorproxy:\n    image: ${MIRRORPROXY_IMAGE:-kudang\u002Fmirrorproxy:latest}\n    container_name: mirrorproxy\n    restart: unless-stopped\n    ports:\n      - \"${MIRRORPROXY_PORT:-3000}:3000\"\n    environment:\n      MIRRORPROXY_PUBLIC_BASE_URL: ${MIRRORPROXY_PUBLIC_BASE_URL:-}\n      MIRRORPROXY_TRUSTED_PROXIES: ${MIRRORPROXY_TRUSTED_PROXIES:-127.0.0.1,::1}\n      MIRRORPROXY_QUOTA_TIMEZONE: ${MIRRORPROXY_QUOTA_TIMEZONE:-local}\n      MIRRORPROXY_ADMIN_PASSWORD: ${MIRRORPROXY_ADMIN_PASSWORD:-}\n      RUST_LOG: ${RUST_LOG:-mirrorproxy_server=info,tower_http=info}\n    volumes:\n      - mirrorproxy-data:\u002Fdata\n    healthcheck:\n      test: [\"CMD\", \"curl\", \"--fail\", \"--silent\", \"--show-error\", \"http:\u002F\u002F127.0.0.1:3000\u002Fhealthz\"]\n      interval: 30s\n      timeout: 5s\n      start_period: 10s\n      retries: 3\n\nvolumes:\n  mirrorproxy-data:\n```\n\nOptionally set a fixed external URL, host port, initial administrator password,\nand trusted reverse-proxy peers in a `.env` file before startup. When the\npublic URL is unset or empty, MirrorProxy derives it from the browser request\naddress. Forwarded headers are accepted only from `MIRRORPROXY_TRUSTED_PROXIES`:\n\n```dotenv\nMIRRORPROXY_PORT=53000\nMIRRORPROXY_PUBLIC_BASE_URL=https:\u002F\u002Fmirror.example.com\n# Keep the defaults for a host-local Nginx\u002FCaddy; use the reverse proxy's\n# Docker-network peer IP or CIDR when it runs in another container.\nMIRRORPROXY_TRUSTED_PROXIES=127.0.0.1,::1\n# Optional: uncomment to set the initial admin password yourself.\n# MIRRORPROXY_ADMIN_PASSWORD=replace-with-a-strong-password\n```\n\n```bash\nMIRRORPROXY_PUBLIC_BASE_URL=https:\u002F\u002Fmirror.example.com docker compose up -d\ndocker compose logs mirrorproxy\ncurl http:\u002F\u002F127.0.0.1:3000\u002Fhealthz\n```\n\nWhen the SQLite database is initialized for the first time, an unset or empty\n`MIRRORPROXY_ADMIN_PASSWORD` makes MirrorProxy generate a random `admin`\npassword and print it prominently in the startup log. Set the variable to a\nnon-empty value to use that password instead; manually configured passwords are\nnot printed. The variable does not reset the password in an existing database.\nKeep the named `mirrorproxy-data` volume when upgrading. To run without\nCompose:\n\n```bash\ndocker run -d --name mirrorproxy --restart unless-stopped \\\n  -p 3000:3000 \\\n  -e MIRRORPROXY_PUBLIC_BASE_URL=https:\u002F\u002Fmirror.example.com \\\n  -e MIRRORPROXY_TRUSTED_PROXIES=127.0.0.1,::1 \\\n  -e MIRRORPROXY_ADMIN_PASSWORD='replace-with-a-strong-password' \\\n  -v mirrorproxy-data:\u002Fdata \\\n  kudang\u002Fmirrorproxy:latest\n```\n\nThe named volume is recommended. If you replace it with a host bind mount such\nas `\u002Fsrv\u002Fmirrorproxy\u002Fdata:\u002Fdata`, that directory must be writable by container\nUID\u002FGID `10001:10001`:\n\n```bash\nsudo install -d -o 10001 -g 10001 -m 0750 \u002Fsrv\u002Fmirrorproxy\u002Fdata\nsudo install -d -o 10001 -g 10001 -m 0750 \u002Fsrv\u002Fmirrorproxy\u002Fdata\u002Fcache\n```\n\nOn an SELinux-enforcing host, append `:Z` to the bind mount. Otherwise SQLite\nmay fail at startup with `code: 14 unable to open database file`.\n\nEnvironment variables such as `MIRRORPROXY_ENABLED_PROXIES`, quota, cache, and\nrate-limit settings work in the container. For a complete TOML configuration,\nmount it read-only and set its path explicitly:\n\n```bash\ndocker run -d --name mirrorproxy --restart unless-stopped \\\n  -p 3000:3000 \\\n  -e MIRRORPROXY_CONFIG=\u002Fetc\u002Fmirrorproxy\u002Fconfig.toml \\\n  -e MIRRORPROXY_LISTEN_ADDR=0.0.0.0:3000 \\\n  -e MIRRORPROXY_DB=\u002Fdata\u002Fmirrorproxy.sqlite3 \\\n  -v mirrorproxy-data:\u002Fdata \\\n  -v \"$PWD\u002Fconfig.toml:\u002Fetc\u002Fmirrorproxy\u002Fconfig.toml:ro\" \\\n  kudang\u002Fmirrorproxy:latest\n```\n\nBuild the current source as a local `linux\u002Famd64` image:\n\n```bash\n.\u002Fscripts\u002Fdocker-build.sh\n```\n\nIf Docker Hub is slow or unavailable, route base-image pulls through a\nMirrorProxy instance:\n\n```bash\nMIRRORPROXY_DOCKER_BASE_REGISTRY=sina.dev\u002Flibrary .\u002Fscripts\u002Fdocker-build.sh\n```\n\nFor a local multi-platform build, register ARM64 emulation once (GitHub Actions\ndoes this automatically). The same image can be pulled through MirrorProxy when\nDocker Hub is unavailable:\n\n```bash\ndocker run --privileged --rm sina.dev\u002Ftonistiigi\u002Fbinfmt --install arm64\n```\n\nThen publish `linux\u002Famd64` and `linux\u002Farm64` manifests after `docker login`:\n\n```bash\n.\u002Fscripts\u002Fdocker-build.sh --push --image \u003Cdockerhub-user>\u002Fmirrorproxy\n```\n\nThe `Docker` GitHub Actions workflow performs the same multi-platform publish.\nSet repository variable `DOCKERHUB_USERNAME` and repository secret\n`DOCKERHUB_TOKEN`; pushing a `v*` tag publishes the semantic-version and\n`latest` tags, while workflow dispatch supports an explicit version.\n\n## GitHub Proxy\n\nMirrorProxy accepts supported GitHub absolute URLs under your own domain:\n\n```text\nhttp:\u002F\u002Fselfhost.com\u002Fhttps:\u002F\u002Fgithub.com\u002Finbjo\u002FConductor\nhttp:\u002F\u002Fselfhost.com\u002Fhttps:\u002F\u002Fgithub.com\u002Finbjo\u002FConductor\u002Freleases\u002Fdownload\u002Fnightly\u002Fconductor-client-linux-amd64.deb\n```\n\nAllowed GitHub-related hosts in this slice:\n\n- `github.com`\n- `api.github.com`\n- `raw.githubusercontent.com`\n- `objects.githubusercontent.com`\n- `codeload.github.com`\n\n## Composer Proxy\n\nConfigure Composer to use MirrorProxy:\n\n```bash\ncomposer config repo.packagist composer http:\u002F\u002Fselfhost.com\u002Fcomposer\ncomposer require monolog\u002Fmonolog\n```\n\nMirrorProxy proxies Packagist metadata and rewrites common GitHub\u002FPackagist download URLs back through your MirrorProxy public base URL.\n\n## Docker \u002F OCI Proxy\n\nUse your MirrorProxy host as the Docker registry:\n\n```bash\ndocker pull selfhost.com\u002Fnginx\ndocker pull selfhost.com\u002Fuser\u002Fimage\ndocker pull selfhost.com\u002Fghcr.io\u002Fuser\u002Fimage\ndocker pull selfhost.com\u002Fquay.io\u002Forg\u002Fimage\ndocker pull selfhost.com\u002Fregistry.k8s.io\u002Fpause:3.8\n```\n\nMapping rules:\n\n- `name` maps to Docker Hub `library\u002Fname`\n- `user\u002Fimage` maps to Docker Hub `user\u002Fimage`\n- `ghcr.io\u002Fuser\u002Fimage` maps to GHCR\n- `quay.io\u002Forg\u002Fimage` maps to Quay\n- `registry.k8s.io\u002Fname` maps to the Kubernetes registry\n\nThe proxy handles public pull-through requests and upstream Bearer token challenges. Private upstream credentials can be configured as described in [Security](#security).\n\n## npm \u002F yarn \u002F pnpm Proxy\n\nConfigure your package manager to use MirrorProxy:\n\n```bash\nnpm config set registry http:\u002F\u002Fselfhost.com\u002Fnpm\nnpm install react\n\nyarn config set npmRegistryServer http:\u002F\u002Fselfhost.com\u002Fnpm\nyarn add react\n\npnpm config set registry http:\u002F\u002Fselfhost.com\u002Fnpm\npnpm add react\n```\n\nMirrorProxy proxies npm package metadata and rewrites `dist.tarball` URLs to keep tarball downloads flowing through `\u002Fnpm`.\n\n## Go Module Proxy\n\nUse MirrorProxy as `GOPROXY`:\n\n```bash\ngo env -w GOPROXY=http:\u002F\u002Fselfhost.com\u002Fgoproxy,direct\ngo list -m github.com\u002Fgin-gonic\u002Fgin@latest\n```\n\nThe Go adapter forwards GOPROXY protocol paths such as `@v\u002Flist`, `.info`, `.mod`, and `.zip` to `proxy.golang.org`.\n\n## Maven Central Proxy\n\nConfigure Maven's user settings to mirror Central through MirrorProxy:\n\n```xml\n\u003Csettings>\n  \u003Cmirrors>\n    \u003Cmirror>\n      \u003Cid>mirrorproxy\u003C\u002Fid>\n      \u003Curl>http:\u002F\u002Fselfhost.com\u002Fmaven\u002F\u003C\u002Furl>\n      \u003CmirrorOf>central\u003C\u002FmirrorOf>\n    \u003C\u002Fmirror>\n  \u003C\u002Fmirrors>\n\u003C\u002Fsettings>\n```\n\nSave this under `~\u002F.m2\u002Fsettings.xml`, or let the CLI write it with rollback protection:\n\n```bash\nmirrorproxy set maven --mirror mirrorproxy --base-url http:\u002F\u002Fselfhost.com\nmvn dependency:resolve\n```\n\nThe Maven adapter streams Maven2 repository paths, including POMs, metadata, artifacts, checksums, and signatures, from Maven Central.\n\n## RubyGems Proxy\n\nConfigure RubyGems to use MirrorProxy as its source:\n\n```yaml\n---\n:sources:\n- http:\u002F\u002Fselfhost.com\u002Frubygems\u002F\n```\n\nSave this under `~\u002F.gemrc`, or let the CLI write it with rollback protection:\n\n```bash\nmirrorproxy set rubygems --mirror mirrorproxy --base-url http:\u002F\u002Fselfhost.com\ngem install rake\n```\n\nThe RubyGems adapter streams the compact index (`\u002Fversions`, `\u002Finfo\u002F*`), legacy indexes, API responses, and `.gem` downloads while preserving Range and ETag headers used by Bundler.\n\n## NuGet v3 Proxy\n\nConfigure NuGet to use MirrorProxy as a v3 package source:\n\n```xml\n\u003C?xml version=\"1.0\" encoding=\"utf-8\"?>\n\u003Cconfiguration>\n  \u003CpackageSources>\n    \u003Cclear \u002F>\n    \u003Cadd key=\"mirrorproxy\" value=\"http:\u002F\u002Fselfhost.com\u002Fnuget\u002Fv3\u002Findex.json\" protocolVersion=\"3\" \u002F>\n  \u003C\u002FpackageSources>\n\u003C\u002Fconfiguration>\n```\n\nSave it to `%APPDATA%\\NuGet\\NuGet.Config` on Windows or `~\u002F.nuget\u002FNuGet\u002FNuGet.Config` on Linux\u002FmacOS. The CLI writes the same file with rollback protection:\n\n```bash\nmirrorproxy set nuget --mirror mirrorproxy --base-url http:\u002F\u002Fselfhost.com\ndotnet restore\n```\n\nThe adapter rewrites NuGet v3 service-index resource URLs to MirrorProxy, then streams flat-container packages, registration metadata, search results, and package downloads through `\u002Fnuget`.\n\n## CPAN Proxy\n\nUse the CPAN static-mirror endpoint with `cpanm`:\n\n```bash\ncpanm --mirror http:\u002F\u002Fselfhost.com\u002Fcpan\u002F --mirror-only Moo\n```\n\nThe CLI can save a rollback-protected CPAN mirror list to `~\u002F.cpan\u002FCPAN\u002FMyConfig.pm`:\n\n```bash\nmirrorproxy set cpan --mirror mirrorproxy --base-url http:\u002F\u002Fselfhost.com\n```\n\nThe adapter streams CPAN indexes and distributions such as `modules\u002F02packages.details.txt.gz` and `authors\u002Fid\u002F...` while rejecting traversal paths.\n\n## CRAN Proxy\n\nSet R's CRAN repository to MirrorProxy:\n\n```r\noptions(repos = c(CRAN = \"http:\u002F\u002Fselfhost.com\u002Fcran\u002F\"))\ninstall.packages(\"digest\")\n```\n\n`mirrorproxy set cran --mirror mirrorproxy --base-url http:\u002F\u002Fselfhost.com` writes a rollback-protected `~\u002F.Rprofile`. Source indexes, archives, and platform binary paths are streamed through `\u002Fcran`.\n\n## Hackage Proxy\n\nConfigure Cabal's user repository to use MirrorProxy:\n\n```yaml\nrepository hackage.haskell.org\n  url: http:\u002F\u002Fselfhost.com\u002Fhackage\u002F\n  secure: True\n```\n\n`mirrorproxy set hackage --mirror mirrorproxy --base-url http:\u002F\u002Fselfhost.com` writes and can restore `~\u002F.cabal\u002Fconfig`. The adapter streams the package index and package tarballs while rejecting traversal paths.\n\n## Rustup Distribution Proxy\n\nPoint Rustup's distribution and self-update roots at MirrorProxy:\n\n```bash\nexport RUSTUP_DIST_SERVER=http:\u002F\u002Fselfhost.com\u002Frustup\nexport RUSTUP_UPDATE_ROOT=http:\u002F\u002Fselfhost.com\u002Frustup\u002Frustup\nrustup update stable\n```\n\nChannel manifests, components, checksums, and signatures are streamed from the official Rust distribution service through normalized paths.\n\n## Julia Package Server Proxy\n\nSet `JULIA_PKG_SERVER=http:\u002F\u002Fselfhost.com\u002Fjulia` before running Julia's package manager. Registry and package-server protocol paths are forwarded to the configured Julia package server.\n\n## LuaRocks Proxy\n\nOn Linux, install from MirrorProxy with `luarocks install --server=http:\u002F\u002Fselfhost.com\u002Fluarocks\u002F \u003Cmodule>`.\n\n## NVM \u002F Node.js Distribution Proxy\n\nOn Linux, point NVM at the proxied Node.js release files before installing a version:\n\n```bash\nexport NVM_NODEJS_ORG_MIRROR=http:\u002F\u002Fselfhost.com\u002Fnvm\u002F\nnvm install --lts\n```\n\n## opam Proxy\n\nOn Linux, configure `opam repository set-url default http:\u002F\u002Fselfhost.com\u002Fopam\u002F`.\n\n## Clojars Proxy\n\nConfigure the Clojure CLI user `deps.edn` to route Clojars through MirrorProxy:\n\n```clojure\n{:mvn\u002Frepos {\"clojars\" {:url \"http:\u002F\u002Fselfhost.com\u002Fclojars\u002F\"}}}\n```\n\n`mirrorproxy set clojars --mirror mirrorproxy --base-url http:\u002F\u002Fselfhost.com` writes and can restore `~\u002F.clojure\u002Fdeps.edn`. The adapter streams Clojars POMs, metadata, and JARs with normalized repository paths only.\n\n## CocoaPods CDN Proxy\n\nUse `source 'http:\u002F\u002Fselfhost.com\u002Fcocoapods\u002F'` in a Podfile when routing the CocoaPods CDN through MirrorProxy. CDN index shards and podspec files are accepted only through normalized paths.\n\n## WinGet Source Proxy\n\nAdd the MirrorProxy endpoint as a pre-indexed WinGet source:\n\n```powershell\nwinget source add --name mirrorproxy --arg http:\u002F\u002Fselfhost.com\u002Fwinget\u002F --type Microsoft.PreIndexed.Package --accept-source-agreements\n```\n\nThe adapter streams the official WinGet source indexes and package metadata through `\u002Fwinget`.\n\n## Pub \u002F Flutter Proxy\n\n```bash\nPUB_HOSTED_URL=http:\u002F\u002Fselfhost.com\u002Fpub\u002F flutter pub get\n```\n\nPub package metadata and official archives stay on MirrorProxy; archive URLs are rewritten only for the official Google Cloud Storage host.\n\n## Anaconda \u002F Conda Proxy\n\nUse MirrorProxy as a Conda channel base, for example `http:\u002F\u002Fselfhost.com\u002Fanaconda\u002Fmain`. The adapter streams `repodata.json` and package artifacts while rejecting traversal paths.\n\n## TeX Live Proxy\n\nUse `http:\u002F\u002Fselfhost.com\u002Ftexlive\u002F` as a TeX Live network installer mirror. The adapter streams `tlpkg\u002Ftexlive.tlpdb` and archive files using normalized paths only.\n\n## GNU ELPA Proxy\n\nUse `http:\u002F\u002Fselfhost.com\u002Felpa\u002F` as an Emacs package archive URL. The adapter streams `archive-contents` and package archives through normalized paths only.\n\n## Nix Binary Cache Proxy\n\nUse `http:\u002F\u002Fselfhost.com\u002Fnix\u002F` as a Nix substituter. `.narinfo` signatures and relative cache URLs remain unchanged, so Nix continues to verify cache signatures normally.\n\n## GNU Guix Substitute Cache Proxy\n\nUse `http:\u002F\u002Fselfhost.com\u002Fguix\u002F` as a Guix substitute URL, for example `guix build --substitute-urls=http:\u002F\u002Fselfhost.com\u002Fguix\u002F hello`. Narinfo signatures and substitute payloads are streamed unchanged, so Guix continues to verify authorized cache keys.\n\n## Flatpak OSTree Proxy\n\nUse `http:\u002F\u002Fselfhost.com\u002Fflatpak\u002F` as a Flatpak remote URL. OSTree summaries and GPG signatures are streamed unchanged, preserving client-side repository verification.\n\n## Homebrew Bottle Proxy\n\nSet `HOMEBREW_BOTTLE_DOMAIN=http:\u002F\u002Fselfhost.com\u002Fhomebrew` before running `brew install`. The default upstream is Homebrew's public GHCR OCI bottle repository; manifest and blob requests are streamed unchanged, including Range requests.\n\n## OS Static Repository Proxy\n\nUse fixed target paths such as `http:\u002F\u002Fselfhost.com\u002Fos\u002Fdebian\u002F`, `\u002Fos\u002Fubuntu\u002F`, `\u002Fos\u002Ffedora\u002F`, `\u002Fos\u002Farchlinux\u002F`, `\u002Fos\u002Fopensuse\u002F`, `\u002Fos\u002Fvoid\u002F`, `\u002Fos\u002Fgentoo\u002F`, `\u002Fos\u002Ffreebsd\u002F`, `\u002Fos\u002Falpine\u002F`, `\u002Fos\u002Fopenwrt\u002F`, `\u002Fos\u002Ftermux\u002F`, `\u002Fos\u002Fkali\u002F`, `\u002Fos\u002Frocky\u002F`, `\u002Fos\u002Falma\u002F`, `\u002Fos\u002Fmanjaro\u002F`, `\u002Fos\u002Fmsys2\u002F`, `\u002Fos\u002Fraspios\u002F`, `\u002Fos\u002Farmbian\u002F`, `\u002Fos\u002Fopeneuler\u002F`, `\u002Fos\u002Fanolis\u002F`, `\u002Fos\u002Fdeepin\u002F`, `\u002Fos\u002Flinuxmint\u002F`, `\u002Fos\u002Fsolus\u002F`, `\u002Fos\u002Ftrisquel\u002F`, `\u002Fos\u002Flinuxlite\u002F`, `\u002Fos\u002Fros\u002F`, `\u002Fos\u002Fnetbsd\u002F`, or `\u002Fos\u002Fopenbsd\u002F`. Only these targets are accepted; each has a separately configurable upstream. Additional OS targets use the `[upstreams.additional_os]` TOML map. Linux Mint defaults to the Kernel.org HTTPS package mirror; the ROS target proxies the ROS 2 Ubuntu APT repository; Solus uses `\u002Fos\u002Fsolus\u002Fpolaris\u002Feopkg-index.xml.xz`.\n\n## Rust Crates Proxy\n\nConfigure Cargo to use MirrorProxy as a sparse registry mirror:\n\n```toml\n[source.crates-io]\nreplace-with = \"mirrorproxy\"\n\n[source.mirrorproxy]\nregistry = \"sparse+http:\u002F\u002Fselfhost.com\u002Fcrates-index\u002F\"\n```\n\nThen fetch dependencies:\n\n```bash\ncargo fetch\n```\n\nMirrorProxy serves a local sparse `config.json` and proxies crate downloads through `\u002Fcrates\u002Fapi\u002Fv1\u002Fcrates\u002F{crate}\u002F{version}\u002Fdownload`.\n\n## pip \u002F PyPI Proxy\n\nConfigure pip to use MirrorProxy:\n\n```bash\npip config set global.index-url http:\u002F\u002Fselfhost.com\u002Fpypi\u002Fsimple\u002F\npip install requests\n```\n\nMirrorProxy proxies PyPI Simple API HTML and rewrites files.pythonhosted.org links back through `\u002Fpypi\u002Ffiles`.\n\n## Configuration\n\nInspect or safely update an explicit TOML configuration file from the CLI. `set`\ncreates a sibling `.bak` backup before atomically replacing the file; use\n`--dry-run` to inspect the change first.\n\n```bash\nmirrorproxy-server --config .\u002Fconfig.toml config get public_base_url\nmirrorproxy-server --config .\u002Fconfig.toml config set public_base_url https:\u002F\u002Fmirror.example\nmirrorproxy-server --config .\u002Fconfig.toml config set quota.monthly_gb 100 --dry-run\n```\n\n## Install the Client\n\nLinux and macOS share one installer. It detects the operating system and CPU\narchitecture, downloads the matching asset from the latest stable GitHub\nRelease, verifies its SHA-256 checksum, and installs `mirrorproxy` under\n`\u002Fusr\u002Flocal\u002Fbin` (using `sudo` only when required).\nThe host must provide `curl`, `tar`, `gzip`, `install`, and either `sha256sum`\nor `shasum`; the installer reports a missing prerequisite before downloading.\n\nInstall it with:\n\n```bash\ncurl -fsSL https:\u002F\u002Fraw.githubusercontent.com\u002Finbjo\u002FMirrorProxy\u002Fmain\u002Fscripts\u002Finstall.sh | sh\n```\n\nTo accelerate both the installer and release asset through a MirrorProxy\ninstance, pass `--mirror`:\n\n```bash\ncurl -fsSL https:\u002F\u002Fsina.dev\u002Fhttps:\u002F\u002Fraw.githubusercontent.com\u002Finbjo\u002FMirrorProxy\u002Fmain\u002Fscripts\u002Finstall.sh | sh -s -- --mirror https:\u002F\u002Fsina.dev\n```\n\nWindows uses a separate PowerShell installer. Windows may block remote scripts\nby default, so allow them for the current PowerShell process only, then run the\ninstaller:\n\n```powershell\nSet-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass -Force\nirm https:\u002F\u002Fraw.githubusercontent.com\u002Finbjo\u002FMirrorProxy\u002Fmain\u002Fscripts\u002Finstall.ps1 | iex\n```\n\nAccelerated Windows installation:\n\n```powershell\nSet-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass -Force\n$env:MIRRORPROXY_DOWNLOAD_MIRROR='https:\u002F\u002Fsina.dev'\nirm https:\u002F\u002Fsina.dev\u002Fhttps:\u002F\u002Fraw.githubusercontent.com\u002Finbjo\u002FMirrorProxy\u002Fmain\u002Fscripts\u002Finstall.ps1 | iex\n```\n\nThe PowerShell installer places `mirrorproxy.exe` under the current user's\nlocal programs directory and adds it to the user `PATH`. Both installers accept\n`MIRRORPROXY_VERSION` for a specific tag and `MIRRORPROXY_INSTALL_DIR` for a\ncustom location. The `latest` route intentionally selects a stable release, not\nthe rolling `nightly` prerelease; it becomes available after the first `v*` tag\nis published.\n\n## Local Source CLI\n\n`mirrorproxy` is a standalone client without Axum, the database, or the embedded\nweb console. GitHub Releases provide Windows, macOS, and Linux builds; the\nindependent `mirrorproxy-server` artifact is released for Linux.\n\nSource commands use the chsrc-style top-level `set`, `get`, `reset`, `list`,\nand `mirrors` forms. The legacy `sources` namespace remains accepted for\nbackward compatibility.\n\n```bash\nmirrorproxy set bun --mirror mirrorproxy --base-url https:\u002F\u002Fsina.dev --scope user\n```\n\n`set` writes supported user-level package-manager configuration, including npm,\npip, Cargo, GitHub HTTPS Git URL rewriting, Go, Composer, Maven, RubyGems,\nNuGet, CPAN, CRAN, Hackage, Clojars, and Anaconda, without invoking the\npackage-manager executable. Before its first change it\nrecords the complete previous file in the platform-native user state directory\n(`~\u002F.local\u002Fstate\u002Fmirrorproxy\u002Fsources\u002F` by default on Linux); `reset` restores\nthat exact file. A non-empty configuration is never\nreplaced unless `--force` is explicit, and reset similarly refuses a file that\nwas changed after the command.\n\n```bash\nmirrorproxy set npm --mirror mirrorproxy --base-url http:\u002F\u002Fselfhost.com\nmirrorproxy set cargo --mirror mirrorproxy --base-url http:\u002F\u002Fselfhost.com\nmirrorproxy set github --mirror mirrorproxy --base-url http:\u002F\u002Fselfhost.com\nmirrorproxy reset npm\nmirrorproxy reset github\n```\n\n`set github` appends a `url.\u003CMirrorProxy>.insteadOf` rule to the user's\n`~\u002F.gitconfig`, so Git clones and package-manager Git dependencies that use\n`https:\u002F\u002Fgithub.com\u002F` automatically go through MirrorProxy. Existing Git config\nis preserved without requiring `--force`, and `reset github` restores the exact\nfile recorded by the rollback. SSH-form GitHub URLs are not rewritten.\n\nUse `--config-root \u002Ftmp\u002Fmirrorproxy-config` for an isolated configuration root in\nautomation or tests. APT, DNF\u002FYUM, pacman, and Docker additionally support\nexplicit `--scope system`: MirrorProxy only manages the relevant configuration\nfile and keeps a rollback record under `\u002Fvar\u002Flib\u002Fmirrorproxy\u002Fsources\u002F` (or the\nsupplied root). APT requires a release codename; system writes normally require\nroot access and are enabled only on Linux hosts.\n\n```bash\nmirrorproxy set apt --mirror tuna --scope system --distribution jammy\nmirrorproxy set apt --mirror mirrorproxy --base-url https:\u002F\u002Fmirror.example --scope system --distribution debian\u002Fbookworm\nmirrorproxy reset apt --scope system\nmirrorproxy set alpine --mirror mirrorproxy --base-url https:\u002F\u002Fmirror.example --scope system --distribution v3.21\nmirrorproxy reset alpine --scope system\nmirrorproxy set xbps --mirror mirrorproxy --base-url https:\u002F\u002Fmirror.example --scope system\nmirrorproxy reset xbps --scope system\nmirrorproxy set zypper --mirror mirrorproxy --base-url https:\u002F\u002Fmirror.example --scope system --distribution distribution\u002Fleap\u002F15.6\nmirrorproxy reset zypper --scope system\nmirrorproxy set gentoo --mirror mirrorproxy --base-url https:\u002F\u002Fmirror.example --scope system\nmirrorproxy reset gentoo --scope system\nmirrorproxy set docker --mirror mirrorproxy --base-url https:\u002F\u002Fmirror.example --scope system\nmirrorproxy reset docker --scope system\n```\n\nDocker writes `\u002Fetc\u002Fdocker\u002Fdaemon.json` with `registry-mirrors`. It never\nreplaces an existing daemon configuration without `--force`, and reset restores\nthe exact previous file. Restart Docker after applying the configuration.\n\nCopy `config.example.toml` and adjust the public URL for your deployment:\n\n```toml\nlisten_addr = \"0.0.0.0:3000\"\npublic_base_url = \"https:\u002F\u002Fmirror.example.com\"\nenabled_proxies = [\"github\", \"composer\", \"oci\", \"npm\", \"nvm\", \"opam\", \"go\", \"maven\", \"rubygems\", \"rustup\", \"nuget\", \"cpan\", \"cran\", \"hackage\", \"julia\", \"luarocks\", \"clojars\", \"cocoapods\", \"pub\", \"anaconda\", \"texlive\", \"winget\", \"elpa\", \"nix\", \"guix\", \"flatpak\", \"homebrew\", \"os\", \"crates\", \"pypi\"]\n\n[upstreams]\ngithub = \"https:\u002F\u002Fgithub.com\"\ngithub_raw = \"https:\u002F\u002Fraw.githubusercontent.com\"\npackagist = \"https:\u002F\u002Frepo.packagist.org\"\ndocker_hub = \"https:\u002F\u002Fregistry-1.docker.io\"\nghcr = \"https:\u002F\u002Fghcr.io\"\nquay = \"https:\u002F\u002Fquay.io\"\nkubernetes = \"https:\u002F\u002Fregistry.k8s.io\"\nnpm = \"https:\u002F\u002Fregistry.npmjs.org\"\nnvm = \"https:\u002F\u002Fnodejs.org\u002Fdist\"\nopam = \"https:\u002F\u002Fopam.ocaml.org\"\ngo_proxy = \"https:\u002F\u002Fproxy.golang.org\"\nmaven = \"https:\u002F\u002Frepo.maven.apache.org\u002Fmaven2\"\nrubygems = \"https:\u002F\u002Frubygems.org\"\nrustup = \"https:\u002F\u002Fstatic.rust-lang.org\"\nnuget = \"https:\u002F\u002Fapi.nuget.org\"\ncpan = \"https:\u002F\u002Fcpan.metacpan.org\"\ncran = \"https:\u002F\u002Fcloud.r-project.org\"\nhackage = \"https:\u002F\u002Fhackage.haskell.org\"\njulia = \"https:\u002F\u002Fpkg.julialang.org\"\nluarocks = \"https:\u002F\u002Fluarocks.org\"\nclojars = \"https:\u002F\u002Frepo.clojars.org\"\ncocoapods = \"https:\u002F\u002Fcdn.cocoapods.org\"\npub_repository = \"https:\u002F\u002Fpub.dev\"\nanaconda = \"https:\u002F\u002Frepo.anaconda.com\u002Fpkgs\"\ntexlive = \"https:\u002F\u002Fmirrors.ctan.org\u002Fsystems\u002Ftexlive\u002Ftlnet\"\nwinget = \"https:\u002F\u002Fcdn.winget.microsoft.com\"\nelpa = \"https:\u002F\u002Felpa.gnu.org\u002Fpackages\"\nnix = \"https:\u002F\u002Fcache.nixos.org\"\nguix = \"https:\u002F\u002Fci.guix.gnu.org\"\nflatpak = \"https:\u002F\u002Fdl.flathub.org\u002Frepo\"\nhomebrew = \"https:\u002F\u002Fghcr.io\u002Fv2\u002Fhomebrew\u002Fcore\"\nalpine = \"https:\u002F\u002Fdl-cdn.alpinelinux.org\u002Falpine\"\nopenwrt = \"https:\u002F\u002Fdownloads.openwrt.org\"\ntermux = \"https:\u002F\u002Fpackages.termux.dev\u002Fapt\u002Ftermux-main\"\ndebian = \"https:\u002F\u002Fdeb.debian.org\u002Fdebian\"\nubuntu = \"https:\u002F\u002Farchive.ubuntu.com\u002Fubuntu\"\nfedora = \"https:\u002F\u002Fdownload.fedoraproject.org\u002Fpub\u002Ffedora\u002Flinux\"\narchlinux = \"https:\u002F\u002Fgeo.mirror.pkgbuild.com\"\nopensuse = \"https:\u002F\u002Fdownload.opensuse.org\"\nvoid = \"https:\u002F\u002Frepo-default.voidlinux.org\"\ngentoo = \"https:\u002F\u002Fdistfiles.gentoo.org\"\nfreebsd = \"https:\u002F\u002Fpkg.freebsd.org\"\ncrates_index = \"https:\u002F\u002Findex.crates.io\"\ncrates_api = \"https:\u002F\u002Fcrates.io\"\npypi_simple = \"https:\u002F\u002Fpypi.org\u002Fsimple\"\npypi_files = \"https:\u002F\u002Ffiles.pythonhosted.org\"\n```\n\n`public_base_url` is used by the web console and metadata rewriters. When it is unset or empty, MirrorProxy derives it from each browser request's host and scheme (including `X-Forwarded-Host` and `X-Forwarded-Proto`). Set it to an external URL to use one fixed address instead.\n\nCommon environment overrides:\n\n```bash\nMIRRORPROXY_CONFIG=\u002Fetc\u002Fmirrorproxy\u002Fconfig.toml\nMIRRORPROXY_DB=\u002Fvar\u002Flib\u002Fmirrorproxy\u002Fmirrorproxy.sqlite3\nMIRRORPROXY_LISTEN_ADDR=0.0.0.0:3000\nMIRRORPROXY_PUBLIC_BASE_URL=https:\u002F\u002Fmirror.example.com\nMIRRORPROXY_TRUSTED_PROXIES=127.0.0.1,::1,172.18.0.0\u002F16\nMIRRORPROXY_ENABLED_PROXIES=github,composer,oci,npm,nvm,opam,go,maven,rubygems,rustup,nuget,cpan,cran,hackage,julia,luarocks,clojars,cocoapods,pub,anaconda,texlive,winget,elpa,nix,guix,flatpak,homebrew,os,crates,pypi\nMIRRORPROXY_REQUEST_TIMEOUT_SECS=60\nMIRRORPROXY_RATE_LIMIT_ENABLED=true\nMIRRORPROXY_RATE_LIMIT_REQUESTS_PER_MINUTE=600\nMIRRORPROXY_CACHE_ENABLED=true\nMIRRORPROXY_CACHE_DIRECTORY=\u002Fvar\u002Fcache\u002Fmirrorproxy\nMIRRORPROXY_CACHE_MAX_ENTRY_MB=8\n```\n\nMirrorProxy validates a non-empty `public_base_url`, all upstream URLs, enabled proxy names, and timeout values during startup. Invalid configuration fails fast with a field-specific error.\n\nOptional disk caching is disabled by default. When enabled, it stores only successful public GET responses with an explicit `Content-Length` no larger than `cache.max_entry_mb`; `cache.max_total_mb` bounds disk usage and evicts least-recently-used entries. Requests carrying `Authorization`, `Cookie`, or `Range` bypass the cache. Large or unknown-length responses stay streamed and are never buffered for caching.\n\nOn the first startup, MirrorProxy creates its SQLite database and prints a one-time\nrandom password for the `admin` account in the local startup log. When\n`MIRRORPROXY_ADMIN_PASSWORD` has a value, it uses that value instead. Use the\npassword with `POST \u002Fapi\u002Fadmin\u002Flogin`, then send the returned token as\n`Authorization: Bearer \u003Ctoken>` to protected endpoints such as `GET\n\u002Fapi\u002Fadmin\u002Fconfig`. The password is stored only as an Argon2 hash; keep the\nstartup output private.\n\n`PUT \u002Fapi\u002Fadmin\u002Fconfig` accepts a validated full configuration document and\npersists it in SQLite with an audit record. Public URL, enabled adapters,\nupstreams, quota, and rate-limit settings apply to new requests immediately.\nChanging `timeout.request_secs` is persisted but reported as restart-required;\n`listen_addr` and `database_path` must be changed in the service configuration\nand cannot be changed through this API.\n\n`GET \u002Fapi\u002Fadmin\u002Fstats` returns the current configured-month summary, quota\nremaining bytes, per-day\u002Fper-target traffic points, and the ten busiest proxy\ntargets. It requires the same administrator Bearer token.\n\n`POST \u002Fapi\u002Fadmin\u002Fpassword` accepts `current_password` and a new password of at\nleast 12 characters. A successful change revokes every administrator session,\nincluding the one that made the request.\n\nOptional global rate limiting can be enabled with:\n\n```toml\n[rate_limit]\nenabled = true\nrequests_per_minute = 600\n```\n\nWhen the limit is exceeded, MirrorProxy returns `429 Too Many Requests` with a `Retry-After` header.\n\n## Traffic Accounting and Monthly Quota\n\nEvery proxied response is counted after its body has been streamed to the client;\ndownloads are never buffered merely for accounting. SQLite keeps daily per-target\nrequest\u002Fbyte\u002Ferror totals and an aggregate monthly byte total. Health checks,\nthe web console, and management APIs are not counted or blocked.\n\n```toml\n[quota]\nenabled = true\nmonthly_gb = 500\ntimezone = \"Asia\u002FTaipei\" # or \"local\"\non_exceeded = \"stop_proxy\" # use \"throttle\" for HTTP 429 instead\n```\n\nOnce the sent-body total reaches the monthly limit, new proxy requests receive\n`503` (`stop_proxy`) or `429` (`throttle`) while the public and management\nsurfaces stay available. A new calendar month in the configured timezone starts\nwith a fresh quota automatically.\n\n## Development\n\nBuild the web console:\n\n```bash\ncd web\nnpm ci\nnpm run build\n```\n\nRun Rust tests:\n\n```bash\ncargo test\n```\n\nRun the full local check:\n\n```bash\ncargo fmt --check\ncargo clippy --workspace --all-targets -- -D warnings\ncargo test\n```\n\nGitHub Actions runs formatting, clippy, Rust tests, the frontend production build, browser end-to-end tests, and native client tests on Linux, Windows, and macOS. Tagging `v*` builds three-platform client artifacts plus Linux server artifacts and publishes a GitHub release with per-artifact checksums and `SHA256SUMS`.\n\nFor a local real-client protocol check (Git, npm\u002Fyarn\u002Fpnpm, Go, Cargo, pip, CPAN cpanm, RubyGems, Maven, NuGet, CRAN, Cabal\u002FHackage, LuaRocks, and Composer, with optional Docker), run:\n\n```bash\n.\u002Fscripts\u002Fsmoke-clients.sh\n```\n\nThe script starts a temporary local server, uses temporary client homes\u002Fcaches, and removes them on exit.\n\n### Verified platforms and clients\n\nThe following matrix records checks that were actually run; a plain HTTP GET\u002FHEAD\nprobe is not presented as a native-client test. The 2026-07-16 OS checks pulled\ntheir container images and package repositories through `sina.dev`. Images that\ncan run the Linux client also exercised the one-line installer, source changes,\nindex refreshes, and a real package download.\n\n| Verification level | Verified targets |\n| --- | --- |\n| Native language\u002Fdevelopment clients | Git, npm, Yarn, pnpm, Go modules, Cargo, pip, CPAN\u002Fcpanm, RubyGems, Maven, NuGet, CRAN\u002FR, Cabal\u002FHackage, LuaRocks, Composer, and Docker\u002FOCI |\n| Native package manager in the matching OS container | Debian 12 APT, Ubuntu 24.04 APT, Fedora 42 DNF, Arch Linux pacman, Alpine 3.21 apk, openSUSE Leap 15.6 zypper, Void Linux xbps, Gentoo emerge, Kali rolling APT, Rocky Linux 9 DNF, AlmaLinux 9 DNF, Manjaro pacman, openEuler 24.03 LTS DNF, Anolis OS 8.8 DNF, Deepin 23 APT, ROS 2 Jazzy APT, OpenWrt 24.10.5 opkg, and Termux x86_64 APT |\n| Compatible package-manager container | Linux Mint, Trisquel, and Linux Lite through APT; Raspberry Pi OS and Armbian through arm64 APT indexes and packages; MSYS2 through its mingw64 repository with pacman |\n| Public protocol endpoint only | FreeBSD, Solus, NetBSD, and OpenBSD; their native userlands\u002Fkernels cannot run on a Linux Docker daemon, so these are not labelled native package-manager tests |\n\nThe OS checks download at least one real package, not only repository metadata:\n`.deb` for Debian-family targets, `.rpm` for Fedora\u002FRHEL-family targets,\n`.pkg.tar.zst` for Arch\u002FManjaro\u002FMSYS2, `.apk` for Alpine, `.ipk` for OpenWrt,\n`.xbps` for Void, and a distfile through `emerge --fetchonly` for Gentoo. When a\nminimal image lacks CA certificates, `tar`, or `gzip`, only those bootstrap\ndependencies may be installed from the image's original repository before its\nsources are cleared and MirrorProxy is tested.\n\nRun the repeatable core OS package-manager matrix with:\n\n```bash\n.\u002Fscripts\u002Fsmoke-os-clients.sh\n```\n\nThe default matrix covers Debian, Ubuntu, Fedora, Arch Linux, Alpine, openSUSE,\nVoid, and Gentoo. Select a subset with `MIRRORPROXY_OS_SMOKE_TARGETS`. To verify\nan unpublished client fix, point `MIRRORPROXY_OS_SMOKE_CLIENT_BINARY` at a local\nstatic client. The script still runs the public one-line installer first, then\nuses the candidate binary for the source-change regression.\n\n## Static Linux Build\n\nOn Linux:\n\n```bash\n.\u002Fbuild.sh\n```\n\nThe script builds the web console first, then builds `mirrorproxy-server` and `mirrorproxy` release binaries for `x86_64-unknown-linux-musl`.\nInstall `musl-tools` first so `musl-gcc` is available.\n\n## Reverse Proxy Deployment\n\nMirrorProxy should usually run behind a TLS reverse proxy. Leave `public_base_url` empty to derive the external scheme and host per request, so changing `a.example.com` to `b.example.com` only requires reloading the reverse proxy. Use a fixed `public_base_url` for path-prefix deployments such as `https:\u002F\u002Fexample.com\u002Fmirrorproxy`.\n\nForwarded headers are accepted only from `trusted_proxies`, which defaults to `127.0.0.1` and `::1`. Add the proxy's actual peer IP or CIDR when it runs in another host\u002Fcontainer. Configure the proxy to **overwrite** `Host`, `X-Forwarded-Host`, and `X-Forwarded-Proto`; never pass client-supplied values through. Keep MirrorProxy's listener private to the proxy where possible.\n\n```toml\n# IPs and CIDRs are supported. This setting takes effect immediately in the admin console.\ntrusted_proxies = [\"127.0.0.1\", \"::1\", \"172.18.0.0\u002F16\"]\n```\n\nEquivalent environment setting: `MIRRORPROXY_TRUSTED_PROXIES=127.0.0.1,::1,172.18.0.0\u002F16`.\n\nNginx example:\n\n```nginx\nserver {\n    listen 443 ssl http2;\n    server_name mirror.example.com;\n\n    client_max_body_size 0;\n    proxy_request_buffering off;\n    proxy_buffering off;\n\n    location \u002F {\n        proxy_pass http:\u002F\u002Fselfhost.com;\n        proxy_http_version 1.1;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Host $host;\n        proxy_set_header X-Forwarded-Proto $scheme;\n    }\n}\n```\n\nCaddy example:\n\n```caddyfile\nmirror.example.com {\n    reverse_proxy 127.0.0.1:3000 {\n        flush_interval -1\n    }\n}\n```\n\nCaddy overwrites the standard forwarded headers automatically. For a separate container\u002Fnetwork, add Caddy's peer address or subnet to `trusted_proxies`.\n\nTraefik (Docker labels) example:\n\n```yaml\nlabels:\n  - traefik.enable=true\n  - traefik.http.routers.mirrorproxy.rule=Host(`mirror.example.com`)\n  - traefik.http.routers.mirrorproxy.entrypoints=websecure\n  - traefik.http.routers.mirrorproxy.tls=true\n  - traefik.http.services.mirrorproxy.loadbalancer.server.port=3000\n```\n\nTraefik supplies `X-Forwarded-Host` and `X-Forwarded-Proto`; set `trusted_proxies` to its Docker-network address\u002Frange, not the public client range.\n\nApache HTTP Server example:\n\n```apache\nProxyPreserveHost On\nProxyPass \u002F http:\u002F\u002F127.0.0.1:3000\u002F\nProxyPassReverse \u002F http:\u002F\u002F127.0.0.1:3000\u002F\nRequestHeader set X-Forwarded-Host \"%{HTTP_HOST}s\"\nRequestHeader set X-Forwarded-Proto \"https\"\n```\n\nHAProxy example:\n\n```haproxy\nfrontend https_in\n    bind :443 ssl crt \u002Fetc\u002Fhaproxy\u002Fcerts\u002Fmirror.example.com.pem\n    http-request set-header X-Forwarded-Host %[req.hdr(Host)]\n    http-request set-header X-Forwarded-Proto https\n    default_backend mirrorproxy\n\nbackend mirrorproxy\n    server app 127.0.0.1:3000\n```\n\nEnvoy example:\n\n```yaml\nstatic_resources:\n  clusters:\n    - name: mirrorproxy\n      connect_timeout: 1s\n      type: STATIC\n      load_assignment:\n        cluster_name: mirrorproxy\n        endpoints:\n          - lb_endpoints:\n              - endpoint:\n                  address:\n                    socket_address: { address: 127.0.0.1, port_value: 3000 }\n```\n\nUse Envoy's HTTP connection manager with `use_remote_address: true` and ensure its generated `x-forwarded-proto` \u002F authority headers reach MirrorProxy; add Envoy's peer IP to `trusted_proxies`.\n\nFor Docker\u002FOCI and large release files, keep request buffering disabled in the reverse proxy so large blobs stream instead of being fully buffered.\n\n## Security Notes\n\n- MirrorProxy is not an open proxy.\n- GitHub absolute URL proxying is restricted to a small allowlist of GitHub-related hosts.\n- Hop-by-hop headers are filtered.\n- Private upstream registries can use static Basic or Bearer credentials from\n  `upstream_auth` in the service TOML. Credentials are matched only to the\n  configured upstream host, are never exposed through the admin API or SQLite,\n  and client-supplied `Authorization` and `Cookie` headers are never forwarded.\n- To use a client's own GitHub, npm, or PyPI token with its matching upstream,\n  set `forward_client_authorization = true`. This is disabled by default; a\n  configured static `upstream_auth` credential always takes precedence.\n- Request-level diagnostic events are retained for 30 days by default; set\n  `quota.request_event_retention_days` (or `MIRRORPROXY_REQUEST_EVENT_RETENTION_DAYS`)\n  to tune the retention window.\n\n## Roadmap\n\nVersion 1.0 already includes the multi-ecosystem and OS repository adapters,\nSQLite-backed administration and traffic accounting, global monthly quota,\nrate limiting, bounded disk caching, native client releases, the embedded web\nconsole, documented native-client and public-protocol smoke matrices, and\nDocker deployment support.\n\nPlanned v1.x work:\n\n- Maintain signed multi-architecture Docker Hub images with SBOM and provenance\n  attestations.\n- Add per-user or per-subdomain traffic ownership and independent quotas.\n- Expand real native-client smoke coverage for the remaining catalog targets,\n  especially Windows, macOS, and less common language ecosystems.\n- Add Prometheus\u002FOpenTelemetry metrics, structured request tracing, and alerting\n  examples without recording credentials.\n- Complete additional package-manager-specific source editing and rollback\n  behavior for the remaining catalog targets.\n- Evaluate high-availability metadata storage while retaining SQLite as the\n  zero-dependency default.\n\n### Spark volunteer mirror network\n\nThe planned **Spark** network will let operators voluntarily contribute public\nMirrorProxy capacity while clients discover and fail over between nodes without\ndepending on a single mirror endpoint:\n\n- `spark-mirrors.sina.dev` DNS TXT records publish only a small, versioned set\n  of core bootstrap peers; the DNS record is not a global node list.\n- Bootstrap peers introduce clients to signed node advertisements through a\n  libp2p control plane based on Kademlia discovery, Identify, Ping, and optional\n  Gossipsub events.\n- A local MirrorProxy agent will score eligible nodes by health, latency,\n  declared capacity, and recent request success, then route package-manager\n  requests with circuit breaking and failover.\n- Volunteer nodes remain restricted MirrorProxy adapters rather than arbitrary\n  URL forward proxies. Peer identities, expiring advertisements, integrity\n  checks, bandwidth limits, and operator-controlled quotas are required.\n\nSpark deliberately targets directly reachable servers. Every volunteer node\nmust have a public domain name, a valid publicly trusted HTTPS certificate, and\nthe required inbound ports open from the Internet. Nodes behind NAT or CGNAT\nare out of scope; the roadmap does not include relay bandwidth, UPnP, NAT-PMP,\nhole punching, or a NAT-traversing data plane.\n",2,"2026-07-20 02:30:03","CREATED_QUERY"]