
Легковесный кэширующий прокси для реестров пакетов.
Кэширующий прокси для реестров пакетов. Ускоряет загрузку пакетов за счёт локального кэширования артефактов, снижая потребление пропускной способности и повышая надёжность.
Большинство атак на цепочку поставок полагаются на скорость: вредоносная версия публикуется и потребляется автоматизированными конвейерами в течение нескольких минут, прежде чем кто-либо это заметит. Функция периода ожидания добавляет карантинный период для недавно опубликованных версий. Когда она включена, прокси удаляет версии из ответов метаданных до тех пор, пока они не превысят настраиваемый порог по времени.```yaml cooldown: default: "3d" # hide versions published less than 3 days ago ecosystems: npm: "7d" # npm gets a longer window cargo: "0" # disable for cargo packages: "pkg:npm/lodash": "0" # exempt trusted packages
3-дневный период ожидания означает, что когда `lodash` публикует версию `4.18.0`, ваши сборки продолжают использовать `4.17.21`, пока не пройдёт 3 дня. Если новый релиз окажется скомпрометированным, вы никогда не подвергались риску.
Порядок разрешения: переопределение пакета, затем переопределение экосистемы, затем глобальное значение по умолчанию. Это позволяет задать консервативное значение по умолчанию и сделать исключения для пакетов, где нужны более быстрые обновления. Полный справочник по конфигурации см. в [docs/configuration.md](https://github.com/git-pkgs/proxy/blob/main/docs/configuration.md).
## Сканирование артефактов
Период ожидания учитывает только временную метку публикации версии — он никогда не проверяет сами байты. Сканирование артефактов закрывает этот пробел: когда оно включено, каждый артефакт помещается в хранилище и сканируется одной или несколькими внешними службами (trivy, ClamAV, Wiz или любой другой, поддерживающей небольшой контракт HTTP/JSON), прежде чем он будет зафиксирован в кэше и отдан клиентам.```yaml
scanning:
enabled: true
signing_key: ${PROXY_SCANNING_SIGNING_KEY}
scanners:
- name: clamav
url: http://clamav-adapter:8080/scan
mode: block # a block verdict deletes the artifact and returns 403
- name: trivy
url: http://trivy-adapter:8081/scan
mode: monitor # findings are logged, never gate caching
ecosystems: [npm, pypi]
Прокси никогда не загружает байты артефактов в сканер. Каждый сканер уведомляется с метаданными пакета и кратковременной подписанной ссылкой; сканер сам загружает байты из собственного хранилища прокси. Сканеры работают параллельно, и первый сканер в режиме block, сообщивший вердикт «не разрешено», немедленно побеждает, отменяя остальные. Полный справочник по конфигурации и HTTP-контракт сканера см. в docs/configuration.md.
| Реестр | Язык/Платформа | Cooldown | Завершено |
|---|---|---|---|
| npm | JavaScript | Да | ✓ |
| Cargo | Rust | Да | ✓ |
| RubyGems | Ruby | Да | ✓ |
| Go proxy | Go | ✓ | |
| Hex | Elixir | Да* | ✓ |
| pub.dev | Dart | Да | ✓ |
| PyPI | Python | Да | ✓ |
| Maven | Java | ✓ | |
| Gradle Build Cache | Java/Kotlin | ✓ | |
| NuGet | .NET | Да | ✓ |
| Composer | PHP | Да | ✓ |
| Conan | C/C++ | ✓ | |
| Conda | Python/R | Да | ✓ |
| CRAN | R | ✓ | |
| Julia | Julia | ✓ | |
| Swift | Swift | ✓ | |
| Container | Docker/OCI | ✓ | |
| Homebrew | macOS/Linux | ✓ | |
| Debian | Debian/Ubuntu | ✓ | |
| RPM | RHEL/Fedora | ✓ | |
| Alpine | Alpine Linux | ✓ | |
| Arch | Arch Linux | ✗ |
Cooldown требует наличия временных меток публикации в метаданных. Реестры без «Да» в столбце cooldown либо не предоставляют временные метки, либо ещё не подключены.
* Cooldown для Hex требует отключения проверки подписи реестра (HEX_NO_VERIFY_REPO_ORIGIN=1), поскольку прокси перекодирует protobuf-полезную нагрузку.
brew install git-pkgs/git-pkgs/proxy
Или скачайте бинарный файл со [страницы релизов](https://github.com/git-pkgs/proxy/releases).
### Helm
Установите чарт из GHCR, указав публичный URL, который клиенты менеджеров пакетов
будут использовать для доступа к прокси:```bash
helm install proxy oci://ghcr.io/git-pkgs/charts/proxy \
--set config.data.base_url=https://proxy.example.com
По умолчанию чарт развёртывает одну реплику с постоянным томом на 10 GiB,
используя SQLite и файловое хранилище артефактов в /data. См.
deploy/charts/proxy/values.yaml для параметров конфигурации ingress,
внешней базы данных и объектного хранилища.
go build -o proxy ./cmd/proxy
./proxy
./proxy -listen :3000 -base-url https://proxy.example.com
Прокси теперь запущен. Настройте ваши менеджеры пакетов для его использования.
## OpenAPI (Swagger)
В этом репозитории используется swaggo для генерации спецификации OpenAPI из аннотированных обработчиков.
Сгенерируйте спецификацию:```bash
go install github.com/swaggo/swag/cmd/swag@latest
go generate ./internal/server
Сгенерированные файлы записываются в docs/swagger/.
Когда прокси запущен, получите актуальную спецификацию по адресу:
http://localhost:8080/openapi.jsonИли замените http://localhost:8080 на настроенный вами базовый URL. Эта ссылка также отображается на панели управления.
Создайте или отредактируйте ~/.npmrc:```
registry=http://localhost:8080/npm/
Или задайте для каждого проекта в `.npmrc`:```
registry=http://localhost:8080/npm/
Или используйте переменную окружения:```bash npm_config_registry=http://localhost:8080/npm/ npm install
### Cargo
Создайте или отредактируйте `~/.cargo/config.toml`:```toml
[source.crates-io]
replace-with = "proxy"
[source.proxy]
registry = "sparse+http://localhost:8080/cargo/"
Или задайте для каждого проекта в .cargo/config.toml в корне вашего проекта.
Задайте источник gem в вашем Gemfile:```ruby
source "http://localhost:8080/gem"
Или настройте глобально:```bash
gem sources --add http://localhost:8080/gem/
bundle config mirror.https://rubygems.org http://localhost:8080/gem
Задайте переменную окружения GOPROXY:```bash export GOPROXY=http://localhost:8080/go,direct
Или в профиле вашей оболочки для сохранения.
### Homebrew
Направьте JSON API и домен артефактов Homebrew на прокси:```bash
export HOMEBREW_API_DOMAIN=http://localhost:8080/homebrew
export HOMEBREW_ARTIFACT_DOMAIN=http://localhost:8080
Домен артефактов проксирует манифесты и blob-объекты bottle по пути /v2/homebrew/core/. Маршрутизация GHCR ограничена этим репозиторием. Архивы исходников, загрузки приложений cask, артефакты пользовательских tap и устаревшие flat-file зеркала bottle используют обычные резервные URL Homebrew. Оставьте резервный режим включённым, не задавая HOMEBREW_ARTIFACT_DOMAIN_NO_FALLBACK.
Включите cache_metadata или задайте PROXY_CACHE_METADATA=true, чтобы сохранять ответы JSON API Homebrew для офлайн-резерва. Blob-объекты bottle и их OCI-манифесты кэшируются и без этой настройки.
По умолчанию upstream-источниками являются https://formulae.brew.sh/api для JSON API и https://ghcr.io для артефактов. Чтобы связать этот прокси с другим прокси, настройте его конечные точки Homebrew в качестве upstream:```yaml
upstream:
homebrew_api: "https://upstream-proxy.example.com/homebrew"
homebrew_artifact: "https://upstream-proxy.example.com"
Эквивалентные переменные окружения — `PROXY_UPSTREAM_HOMEBREW_API` и `PROXY_UPSTREAM_HOMEBREW_ARTIFACT`.
### Hex (Elixir)
Настройте в `~/.hex/hex.config`:```erlang
{default_url, <<"http://localhost:8080/hex">>}.
Или задайте переменную окружения:```bash export HEX_MIRROR=http://localhost:8080/hex
### pub.dev (Dart/Flutter)
Задайте переменную окружения PUB_HOSTED_URL:```bash
export PUB_HOSTED_URL=http://localhost:8080/pub
Настройте pip для использования прокси:```bash pip install --index-url http://localhost:8080/pypi/simple/ package_name
Или задайте в `~/.pip/pip.conf`:```ini
[global]
index-url = http://localhost:8080/pypi/simple/
Добавьте в ваш ~/.m2/settings.xml:```xml
proxy
central
http://localhost:8080/maven/
Эндпоинт `/maven/` использует Maven Central в качестве основного upstream-источника и переключается на Gradle Plugin Portal для метаданных маркеров Gradle-плагинов и связанных артефактов, когда основной upstream возвращает «не найдено».
Для разрешения Gradle-плагинов через тот же прокси-эндпоинт:```kotlin
pluginManagement {
repositories {
maven(url = "http://localhost:8080/maven/")
}
}
Настройте в settings.gradle(.kts):```kotlin
buildCache {
local {
enabled = false
}
remote {
url = uri("http://localhost:8080/gradle/")
push = true
}
}
### NuGet
Настройте в `nuget.config`:```xml
<configuration>
<packageSources>
<clear />
<add key="proxy" value="http://localhost:8080/nuget/v3/index.json" />
</packageSources>
</configuration>
Или используйте CLI:```bash dotnet nuget add source http://localhost:8080/nuget/v3/index.json -n proxy
### Composer (PHP)
Настройте в `composer.json`:```json
{
"repositories": [
{
"type": "composer",
"url": "http://localhost:8080/composer"
}
]
}
Или задайте глобально:```bash composer config -g repositories.proxy composer http://localhost:8080/composer
### Conan (C/C++)
Добавьте прокси как удалённый репозиторий:```bash
conan remote add proxy http://localhost:8080/conan
conan remote disable conancenter
Или настройте в ~/.conan2/remotes.json.
Настройте в ~/.condarc:```yaml
channels:
Или задайте через команду:```bash
conda config --add channels http://localhost:8080/conda/main
Настройте репозиторий в R:```r options(repos = c(CRAN = "http://localhost:8080/cran"))
Или в `~/.Rprofile` для сохранения:```r
local({
r <- getOption("repos")
r["CRAN"] <- "http://localhost:8080/cran"
options(repos = r)
})
Установите сервер Pkg перед запуском Julia:```bash export JULIA_PKG_SERVER=http://localhost:8080/julia
Или внутри запущенной сессии:```julia
ENV["JULIA_PKG_SERVER"] = "http://localhost:8080/julia"
using Pkg; Pkg.update()
Настройте прокси как реестр по умолчанию для текущего пакета Swift:```bash swift package-registry set --allow-insecure-http http://localhost:8080/swift
Зависимости реестра используют свой идентификатор пакета с областью видимости в `Package.swift`:```swift
dependencies: [
.package(id: "apple.swift-argument-parser", from: "1.2.0")
]
Прокси поддерживает разрешение зависимостей и загрузку исходников. Публикация с помощью
swift package-registry publish не поддерживается.
Настройте Docker на использование прокси в качестве зеркала реестра в /etc/docker/daemon.json:```json
{
"registry-mirrors": ["http://localhost:8080"]
}
Затем перезапустите Docker:```bash
sudo systemctl restart docker
Или загружайте образы напрямую:```bash docker pull localhost:8080/library/nginx:latest
### Helm
Настройте каждый HTTP-репозиторий чартов с именем, затем добавьте соответствующий прокси-URL в Helm:```yaml
upstream:
helm:
bitnami: "https://charts.bitnami.com/bitnami"
| --no-color | Отключить цветной вывод |
| --debug | Включить отладочный вывод |
| --verbose | Включить подробный вывод |
| --silent | Отключить весь вывод, кроме ошибок |
| --version | Показать версию и выйти |
| --help | Показать справку и выйти |```bash
helm repo add bitnami http://localhost:8080/helm/bitnami
helm repo update
helm pull bitnami/nginx
Прокси кэширует `index.yaml`, используя обычные настройки кэша метаданных, и
кэширует архивы чартов после проверки их дайджеста SHA-256 из индекса.
Для чартов, хранящихся в OCI-реестре, настройте именованный OCI-апстрим и добавьте
зарезервированный префикс `upstream/{name}` к ссылке на чарт:```yaml
upstream:
oci:
ghcr: "https://ghcr.io"
git clone https://github.com/example/security-tool.git
cd security-tool
pip install -r requirements.txt
Скопируйте файл конфигурации по умолчанию и измените его в соответствии с вашими потребностями:
cp config.example.yaml config.yaml
Отредактируйте config.yaml, чтобы настроить параметры сканирования, каналы уведомлений и другие опции.
python main.py --daemon
python web.py --port 8080
Затем откройте браузер и перейдите по адресу http://localhost:8080.
python scan.py --path /path/to/scan
python quarantine.py --list
Файл config.yaml содержит следующие основные разделы:
scan: параметры сканирования, включая интервалы и исключения.notifications: настройки уведомлений (email, webhook и т. д.).quarantine: параметры карантина, включая путь хранения.logging: настройки логирования.Этот проект распространяется под лицензией MIT. Подробности см. в файле LICENSE.```bash helm pull oci://localhost:8080/upstream/ghcr/owner/charts/mychart --version 1.0.0 --plain-http
### Debian / APT
Настройте APT для использования прокси в `/etc/apt/sources.list.d/proxy.list`:```
deb http://localhost:8080/debian stable main contrib
Замените существующие записи в sources.list, затем:```bash sudo apt update
По умолчанию вышестоящий сервер — `http://deb.debian.org/debian`. Чтобы проксировать другой репозиторий APT (например, Ubuntu), задайте `upstream.debian` в файле конфигурации или `PROXY_UPSTREAM_DEBIAN` в переменных окружения:```yaml
upstream:
debian: "http://archive.ubuntu.com/ubuntu"
Настройте yum/dnf для использования прокси в /etc/yum.repos.d/proxy.repo:```ini
[proxy-fedora]
name=Fedora via Proxy
baseurl=http://localhost:8080/rpm/releases/$releasever/Everything/$basearch/os/
enabled=1
gpgcheck=0
Затем:```bash
sudo dnf clean all
sudo dnf update
Укажите в /etc/apk/repositories прокси. Имя репозитория по умолчанию
alpine проксирует официальное зеркало (https://dl-cdn.alpinelinux.org/alpine):```
http://localhost:8080/apk/alpine/v3.22/main
http://localhost:8080/apk/alpine/v3.22/community
Затем:```bash
apk update
Индексы репозиториев (v2 APKINDEX.tar.gz и v3 Packages.adb), отделённые
подписи и пакеты отдаются побайтово без изменений, поэтому обычная
проверка подписей apk продолжает работать. Индексы используют кэш метаданных
(metadata_ttl, устаревший fallback); пакеты .apk хранятся в общем
кэше артефактов и остаются доступными, когда вышестоящий сервер недоступен.
Чтобы проксировать другие зеркала или приватные репозитории, настройте именованные upstream'ы
в разделе upstream.apk (это заменяет встроенный по умолчанию; добавьте alpine обратно, если
он всё ещё нужен):```yaml
upstream:
apk:
alpine: "https://dl-cdn.alpinelinux.org/alpine"
private: "https://apk.example.com"
| `-s` | Silent mode. Only print the results. |
| `-v` | Verbose mode. Print all the details. |
| `-d` | Debug mode. Print debug information. |
| `-h` | Show help message. |
| `-V` | Show version. |
| `-c` | Configuration file. |
| `-o` | Output file. |
| `-f` | Output format. |
| `-t` | Timeout. |
| `-p` | Proxy. |
| `-r` | Recursive. |
| `-l` | Log file. |
| `-e` | Exclude. |
| `-i` | Include. |
| `-u` | Update. |
| `-n` | No color. |
| `-q` | Quiet mode. |
| `-x` | Execute. |
| `-y` | Yes. |
| `-z` | Zip. |
| `-a` | All. |
| `-b` | Backup. |
| `-g` | Generate. |
| `-j` | JSON. |
| `-k` | Kill. |
| `-m` | Monitor. |
| `-w` | Watch. |
| `-A` | All. |
| `-B` | Backup. |
| `-C` | Configuration. |
| `-D` | Debug. |
| `-E` | Exclude. |
| `-F` | Format. |
| `-G` | Generate. |
| `-H` | Help. |
| `-I` | Include. |
| `-J` | JSON. |
| `-K` | Kill. |
| `-L` | Log. |
| `-M` | Monitor. |
| `-N` | No color. |
| `-O` | Output. |
| `-P` | Proxy. |
| `-Q` | Quiet. |
| `-R` | Recursive. |
| `-S` | Silent. |
| `-T` | Timeout. |
| `-U` | Update. |
| `-V` | Version. |
| `-W` | Watch. |
| `-X` | Execute. |
| `-Y` | Yes. |
| `-Z` | Zip. |```
http://localhost:8080/apk/private
apk сам добавляет архитектуру и имя индексного файла к каждой строке репозитория.
Настройте именованные универсальные upstream-источники:```yaml upstream: generic: github: "https://github.com" github-api: "https://api.github.com"
Затем перепишите URL-адреса GitHub в настройках mise (`~/.config/mise/config.toml`, mise ≥ 2025.9.3):```toml
[settings.url_replacements]
"regex:^https://github\\.com/([^/]+)/([^/]+)/releases/download/(.+)" = "http://localhost:8080/generic/github/$1/$2/releases/download/$3"
"regex:^https://api\\.github\\.com/(.*)" = "http://localhost:8080/generic/github-api/$1"
Артефакты релизов кэшируются навсегда после первой загрузки и продолжают
устанавливаться, пока GitHub недоступен. Поиск тегов через api.github.com
кэшируется на metadata_ttl и отдаётся устаревшим во время сбоя или ограничения
частоты запросов. Зафиксируйте mise.lock и устанавливайте с помощью
mise install --locked, чтобы для установки закреплённых версий вообще не
требовался вызов API. Добавьте bearer-токен для https://api.github.com
в upstream.auth, если парк машин превышает анонимный лимит запросов GitHub.
Прокси можно настроить через:
-config string Path to configuration file -listen string Address to listen on (default ":8080") -base-url string Public URL of this proxy (default "http://localhost:8080") -storage-url string Storage URL (file://, s3://, gs://, azblob://) -storage-path string Path to artifact storage directory (deprecated, use -storage-url) -database-driver string Database driver: sqlite or postgres (default "sqlite") -database-path string Path to SQLite database file (default "./cache/proxy.db") -database-url string PostgreSQL connection URL -log-level string Log level: debug, info, warn, error (default "info") -log-format string Log format: text, json (default "text") -access-log string Path to the JSONL access log -version Print version and exit
### Переменные окружения```bash
PROXY_LISTEN=:8080
PROXY_BASE_URL=http://localhost:8080
PROXY_UI_URL=http://localhost:8080 # Optional; defaults to PROXY_BASE_URL
PROXY_STORAGE_URL=file:///var/cache/proxy/artifacts
PROXY_DATABASE_DRIVER=sqlite
PROXY_DATABASE_PATH=./cache/proxy.db
PROXY_DATABASE_URL=postgres://user:pass@localhost/proxy?sslmode=disable
PROXY_LOG_LEVEL=info
PROXY_LOG_FORMAT=text
PROXY_ACCESS_LOG_PATH=/var/log/proxy/access.jsonl
PROXY_UPSTREAM_SWIFT=https://tuist.dev/api/registry/swift
listen: ":8080" base_url: "http://localhost:8080"
storage: url: "file:///var/cache/proxy/artifacts" max_size: "10GB" # Optional: evict LRU when exceeded
database: driver: "sqlite" path: "/var/lib/proxy/cache.db"
log: level: "info" format: "text"
access_log: path: "/var/log/proxy/access.jsonl" # Optional JSONL activity log
upstream: npm: "https://registry.npmjs.org" cargo: "https://index.crates.io" swift: "https://tuist.dev/api/registry/swift"
cooldown: default: "3d"
См. [справочник по конфигурации](https://github.com/git-pkgs/proxy/blob/main/docs/configuration.md#upstream-registries) для описания всех ключей upstream, переменных окружения и URL по умолчанию.
Запуск с файлом конфигурации:```bash
./proxy -config /etc/proxy/config.yaml
SQLite используется по умолчанию и хорошо подходит для одноузловых развёртываний. Для многоузловых конфигураций или если вы предпочитаете управляемую базу данных, переключитесь на Postgres:```yaml database: driver: "postgres" url: "postgres://user:password@localhost:5432/proxy?sslmode=disable"
Или через переменные окружения:```bash
PROXY_DATABASE_DRIVER=postgres
PROXY_DATABASE_URL=postgres://user:password@localhost:5432/proxy?sslmode=disable
Прокси автоматически создаёт таблицы при первом запуске.
Прокси может хранить кэшированные артефакты в S3 или любом S3-совместимом сервисе (MinIO, R2 и т. д.) вместо локальной файловой системы.```yaml storage: url: "s3://my-bucket-name?region=us-east-1"
Для S3-совместимых сервисов, таких как MinIO:```yaml
storage:
url: "s3://my-bucket?endpoint=http://localhost:9000&disableSSL=true&s3ForcePathStyle=true"
Установите учётные данные через стандартные переменные среды AWS (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION).
Прокси может хранить кэшированные артефакты в бакете GCS, используя схему URL gs://.```yaml
storage:
url: "gs://my-bucket-name"
Аутентификация использует [Application Default Credentials](https://docs.cloud.google.com/docs/authentication/application-default-credentials), что означает, что учётные данные не нужно встраивать в конфигурацию или окружение. Поддерживаемые источники, по порядку:
- **GKE Workload Identity** — привяжите сервисный аккаунт Kubernetes, запускающий прокси, к сервисному аккаунту Google, у которого есть `roles/storage.objectAdmin` на бакете. Прокси будет автоматически использовать токен рабочей нагрузки.
- **Прикреплённый сервисный аккаунт** на GCE, Cloud Run, Cloud Functions и т. д.
- Переменная окружения **`GOOGLE_APPLICATION_CREDENTIALS`**, указывающая на JSON-файл ключа сервисного аккаунта.
- **`gcloud auth application-default login`** для локальной разработки.
#### Настройка GKE Workload Identity```bash
# 1. Create a Google service account
gcloud iam service-accounts create git-pkgs-proxy \
--project=PROJECT_ID
# 2. Grant it access to the bucket
gsutil iam ch \
serviceAccount:git-pkgs-proxy@PROJECT_ID.iam.gserviceaccount.com:objectAdmin \
gs://my-bucket-name
# 3. Bind the Kubernetes service account to it
gcloud iam service-accounts add-iam-policy-binding \
git-pkgs-proxy@PROJECT_ID.iam.gserviceaccount.com \
--role=roles/iam.workloadIdentityUser \
--member="serviceAccount:PROJECT_ID.svc.id.goog[NAMESPACE/KSA_NAME]"
# 4. Annotate the Kubernetes service account
kubectl annotate serviceaccount KSA_NAME \
--namespace=NAMESPACE \
iam.gke.io/gcp-service-account=git-pkgs-proxy@PROJECT_ID.iam.gserviceaccount.com
Когда включён direct_serve: true, прокси выдаёт HTTP 302-редиректы на предварительно подписанные URL-адреса GCS. Workload Identity не предоставляет закрытый ключ, поэтому бэкенд GCS вызывает IAM Credentials API signBlob. Предоставьте сервисному аккаунту роль создателя токенов на самого себя:```bash
gcloud iam service-accounts add-iam-policy-binding
git-pkgs-proxy@PROJECT_ID.iam.gserviceaccount.com
--role=roles/iam.serviceAccountTokenCreator
--member="serviceAccount:git-pkgs-proxy@PROJECT_ID.iam.gserviceaccount.com"
## Команды CLI
### serve (по умолчанию)
Запустить прокси-сервер. Это команда по умолчанию, если не указана другая.```bash
proxy serve [flags]
proxy [flags] # same as 'proxy serve'
Предварительное заполнение кэша из PURL, файлов SBOM или целых реестров. Полезно для обеспечения доступности в офлайн-режиме или прогрева кэша перед развёртыванием.```bash
proxy mirror pkg:npm/[email protected] pkg:cargo/[email protected]
proxy mirror pkg:npm/lodash
proxy mirror --sbom sbom.cdx.json
proxy mirror --dry-run pkg:npm/lodash
proxy mirror --concurrency 8 pkg:npm/[email protected]
Команда mirror принимает те же флаги хранилища и базы данных, что и `serve`. Уже кэшированные артефакты пропускаются.
API зеркалирования также доступен, когда сервер запущен:```bash
# Start a mirror job
curl -X POST http://localhost:8080/api/mirror \
-H "Content-Type: application/json" \
-d '{"purls": ["pkg:npm/[email protected]"]}'
# Start a mirror job from an inline CycloneDX or SPDX JSON SBOM
curl -X POST http://localhost:8080/api/mirror \
-H "Content-Type: application/json" \
-d '{"sbom":{"bomFormat":"CycloneDX","components":[{"purl":"pkg:npm/[email protected]"}]}}'
# Check job status
curl http://localhost:8080/api/mirror/mirror-1
# Cancel a running job
curl -X DELETE http://localhost:8080/api/mirror/mirror-1
Показать статистику кэша без запуска сервера.```bash
proxy stats
proxy stats -json
proxy stats -database-path /var/lib/proxy/cache.db
proxy stats -database-driver postgres -database-url postgres://user:pass@localhost/proxy
proxy stats -popular 20
Пример вывода:```
Cache Statistics
================
Packages: 45
Versions: 128
Artifacts: 128
Total size: 892.4 MB
Total hits: 1547
Packages by ecosystem:
npm 32
cargo 13
Most popular packages:
1. npm/lodash (342 hits, 24.7 KB)
2. npm/react (198 hits, 89.3 KB)
3. cargo/serde (156 hits, 234.1 KB)
Recently cached:
npm/[email protected] (2024-01-15 14:32, 54.2 KB)
cargo/[email protected] (2024-01-15 14:28, 412.8 KB)
| Конечная точка | Описание |
|---|---|
GET / | Панель управления (веб-интерфейс) |
GET /health | Проверка работоспособности и состояние вышестоящего автоматического выключателя (JSON; HTTP 200 — работоспособен, 503 — неработоспособен) |
GET /stats | Статистика кэша (JSON) |
GET /metrics | Метрики Prometheus |
GET /npm/* | Протокол реестра npm |
GET /cargo/* | Протокол разреженного индекса Cargo |
GET /gem/* | Протокол RubyGems |
GET /go/* | Протокол прокси модулей Go |
GET /hex/* | Протокол Hex.pm |
GET /pub/* | Протокол pub.dev |
GET /pypi/* | Простой/JSON API PyPI |
GET /maven/* | Протокол репозитория Maven |
GET /nuget/* | API NuGet V3 |
GET /composer/* | Протокол Composer/Packagist |
GET /conan/* | Протокол Conan C/C++ |
GET /conda/* | Протокол Conda/Anaconda |
GET /cran/* | Протокол CRAN (R) |
GET /julia/* | Протокол сервера Julia Pkg |
GET /swift/* | Протокол Swift Package Registry v1 |
GET /helm/{repository}/* | Протокол HTTP-репозитория чартов Helm |
GET /homebrew/* | JSON API Homebrew |
GET /v2/* | Протокол реестра OCI/Docker |
GET /v2/homebrew/core/* | Манифесты и блобы бутылок Homebrew core из GHCR |
GET /apk/{repository}/* |
| Конечная точка | Описание |
|---|---|
POST /api/mirror | Запустить задание зеркалирования (тело JSON с purls или встроенным sbom) |
GET /api/mirror/{id} | Получить статус и прогресс задания |
DELETE /api/mirror/{id} | Отменить выполняющееся задание |
Прокси предоставляет REST-конечные точки для обогащения метаданных пакетов, сканирования уязвимостей и обнаружения устаревших версий.
| Конечная точка | Описание |
|---|---|
GET /api/package/{ecosystem}/{name} | Получить метаданные пакета |
GET /api/package/{ecosystem}/{name}/{version} | Получить метаданные версии с уязвимостями |
GET /api/vulns/{ecosystem}/{name} | Получить все уязвимости для пакета |
GET /api/vulns/{ecosystem}/{name}/{version} | Получить уязвимости для конкретной версии |
POST /api/outdated | Проверить несколько пакетов на устаревшие версии |
POST /api/bulk | Массовый поиск метаданных пакетов |
I don't see any content in your message to translate — the "INPUT" section is empty.
Please paste the actual Markdown chunk (143/189) you'd like translated from English to Russian, and I'll return only the translated Markdown, preserving all structure, code, paths, URLs, and identifiers exactly as required.```json
{
"ecosystem": "npm",
"name": "lodash",
"latest_version": "4.17.21",
"license": "MIT",
"license_category": "permissive",
"description": "Lodash modular utilities",
"homepage": "https://lodash.com/",
"repository": "https://github.com/lodash/lodash",
"registry_url": "https://registry.npmjs.org"
}
Response:```json
{
"package": {
"ecosystem": "npm",
"name": "lodash",
"latest_version": "4.17.21",
"license": "MIT",
"license_category": "permissive"
},
"version": {
"ecosystem": "npm",
"name": "lodash",
"version": "4.17.0",
"license": "MIT",
"published_at": "2016-06-17T03:59:56Z",
"yanked": false,
"is_outdated": true
},
"vulnerabilities": [
{
"id": "GHSA-p6mc-m468-83gw",
"summary": "Prototype Pollution in lodash",
"severity": "HIGH",
"cvss_score": 7.4,
"fixed_version": "4.17.12"
}
],
"is_outdated": true,
"license_category": "permissive"
}
curl -X POST http://localhost:8080/api/outdated
-H "Content-Type: application/json"
-d '{
"packages": [
{"ecosystem": "npm", "name": "lodash", "version": "4.17.0"},
{"ecosystem": "pypi", "name": "requests", "version": "2.25.0"}
]
}'
I don't see any content to translate in your message. The INPUT section is empty — there's no Markdown text following it.
Please paste the actual chunk 151 content you'd like translated from English to Russian, and I'll return only the translated Markdown with all structure, code, paths, URLs, and identifiers preserved exactly as-is.```json
{
"results": [
{
"ecosystem": "npm",
"name": "lodash",
"version": "4.17.0",
"latest_version": "4.17.21",
"is_outdated": true
},
{
"ecosystem": "pypi",
"name": "requests",
"version": "2.25.0",
"latest_version": "2.31.0",
"is_outdated": true
}
]
}
curl -X POST http://localhost:8080/api/bulk
-H "Content-Type: application/json"
-d '{
"purls": [
"pkg:npm/[email protected]",
"pkg:pypi/[email protected]"
]
}'
Пожалуйста, предоставьте содержимое чанка для перевода.```json
{
"packages": {
"pkg:npm/lodash": {
"ecosystem": "npm",
"name": "lodash",
"latest_version": "4.17.21",
"license": "MIT",
"license_category": "permissive"
},
"pkg:pypi/requests": {
"ecosystem": "pypi",
"name": "requests",
"latest_version": "2.31.0",
"license": "Apache-2.0",
"license_category": "permissive"
}
}
}
{ "cached_artifacts": 142, "total_size_bytes": 523456789, "total_size": "499.2 MB", "storage_url": "file:///path/to/cache/artifacts", "database_path": "./cache/proxy.db" }
## Как это работает
1. Менеджер пакетов запрашивает метаданные пакета у прокси
2. Прокси получает метаданные из upstream, перезаписывает URL артефактов так, чтобы они указывали на прокси
3. Менеджер пакетов запрашивает артефакт (tarball, crate и т. д.)
4. Прокси проверяет локальный кэш:
- **Попадание в кэш**: отдаёт из локального хранилища
- **Промах кэша**: получает из upstream, сохраняет локально, отдаёт клиенту
5. Последующие запросы того же артефакта обслуживаются из кэша```
┌─────────────┐ ┌─────────┐ ┌──────────┐
│ npm/cargo │────▶│ proxy │────▶│ upstream │
│ client │◀────│ │◀────│ registry │
└─────────────┘ └─────────┘ └──────────┘
│
▼
┌─────────┐
│ cache │
│ storage │
└─────────┘
Прокси предоставляет веб-интерфейс по пути /ui. Отдельная сборка фронтенда не требуется — шаблоны и ресурсы встроены в бинарный файл. GET / перенаправляет на /ui/. Интерфейс смонтирован под собственным префиксом, чтобы обратный прокси мог применять к нему иные правила доступа, чем к эндпоинтам пакетов (например, требовать аутентификацию для PathPrefix(/ui), оставляя /npm, /pypi и т. д. открытыми для сборочных машин).
/ui/) — статистика кэша, популярные пакеты, недавно закэшированные артефакты и обзор уязвимостей./ui/install) — инструкции по настройке для каждой экосистемы, чтобы вам не приходилось искать их здесь./ui/packages) — просмотр всех закэшированных пакетов с фильтрацией по экосистеме и сортировкой по попаданиям, размеру, имени или количеству уязвимостей./ui/search?q=...) — поиск закэшированных пакетов по имени./ui/package/{ecosystem}/{name}) — метаданные, лицензия, уязвимости и список версий для пакета. Можно выбрать две версии для сравнения./ui/package/{ecosystem}/{name}/{version}) — метаданные для конкретной версии, хеш целостности, статус кэша артефакта и счётчики попаданий./ui/package/{ecosystem}/{name}/{version}/browse) — просмотр файлов внутри закэшированных архивов с подсветкой синтаксиса для текстовых файлов и предпросмотром изображений./ui/package/{ecosystem}/{name}/compare/{v1}...{v2}) — параллельное сравнение двух закэшированных версий с отображением добавленных, удалённых и изменённых файлов.Прокси предоставляет метрики Prometheus по пути GET /metrics. Все имена метрик имеют префикс proxy_.
| Метрика | Тип | Метки | Описание |
|---|---|---|---|
proxy_requests_total | counter | ecosystem, status | Ответы прокси по экосистеме пакетов и HTTP-статусу |
proxy_request_duration_seconds | histogram | ecosystem, status | Длительность запроса к прокси |
proxy_cache_hits_total | counter | ecosystem | Попадания в кэш |
proxy_cache_misses_total | counter | ecosystem | Промахи кэша |
proxy_cache_size_bytes | gauge | Общий размер закэшированных артефактов | |
proxy_cached_artifacts_total | gauge | Количество закэшированных артефактов | |
proxy_upstream_fetch_duration_seconds | histogram | ecosystem | Время, затраченное на выборку из upstream |
proxy_upstream_errors_total | counter | ecosystem, error_type | Ошибки выборки из upstream |
proxy_storage_operation_duration_seconds | histogram | operation | Задержка чтения/записи хранилища |
proxy_storage_errors_total | counter | operation | Ошибки чтения/записи хранилища |
proxy_active_requests | gauge | Запросы в обработке | |
proxy_health_probe_failures_total | counter | step | Ошибки проверки работоспособности хранилища по неудавшемуся шагу (write, size, read, verify, delete). |
proxy_circuit_breaker_state |
Размер кэша и количество артефактов обновляются каждые 60 секунд. Состояние предохранителя считывается из загрузчика при каждом сборе метрик /metrics и каждом запросе /health, поэтому proxy_circuit_breaker_trips_total подсчитывает срабатывания, видимые между этими чтениями — предохранитель, который открывается и полностью восстанавливается между двумя сборами, не учитывается. Остальные метрики обновляются при каждом запросе.
Метрики предохранителя содержат по одной серии на каждый upstream-хост, но только для хостов, чей предохранитель срабатывал хотя бы раз с момента запуска. Предохранитель создаётся для каждого хоста, с которого прокси выбирает артефакты, и для некоторых экосистем этот хост берётся из upstream-метаданных, а не из конфигурации (composer берёт его из dist.url пакета, helm — из URL диаграмм в index.yaml), поэтому публикация каждого хоста позволила бы upstream-контенту увеличивать количество серий на всё время жизни процесса. После срабатывания хост продолжает отчитываться, поэтому восстановление всё равно отображается как переход к 0, а не как исчезающая серия. /health не является постоянным временным рядом и перечисляет все предохранители, сработавшие или нет.
Метка registry — это хост URL, с которого был выбран артефакт. Поскольку этот URL может происходить из upstream-метаданных, из него не всегда можно извлечь хост — например, подписанный dist.url, который не удаётся разобрать — и такой предохранитель помечается как hostless-url-<digest>, где digest формируется по значению, взятому заново при запуске. Ни /metrics, ни /health не требуют аутентификации, поэтому URL выборки никогда не публикуется в виде метки или ключа; digest идентифицирует предохранитель на всё время работы процесса, не раскрывая URL за ним и не позволяя сопоставить с ним выбранный URL.
Настройте оповещение на proxy_circuit_breaker_state == 2, сохраняющееся более нескольких минут: пока предохранитель открыт, загрузки артефактов для этого upstream завершаются ошибкой HTTP 502 при каждом промахе кэша, и только один пробный запрос за интервал отката достигает upstream. Закэшированные артефакты продолжают обслуживаться, как и метаданные для той же экосистемы (метаданные не проходят через предохранитель), поэтому установки терпят неудачу так, что это выглядит как частичный сбой upstream.
/health возвращает структурированный JSON-отчёт о работоспособности подсистем. HTTP 200, если все проверки пройдены; 503, если какая-либо не пройдена.```json
{
"status": "ok",
"checks": {
"database": {"status": "ok"},
"storage": {"status": "ok"}
},
"circuit_breakers": {
"registry.npmjs.org": "closed",
"static.crates.io": "open"
}
}
Неудачные проверки включают поле `"error"`. Ошибки хранилища также включают поле `"step"`, указывающее, какой шаг проверки завершился неудачно (`write`, `size`, `read`, `verify`, `delete`). Когда проверка базы данных завершается неудачно, запись хранилища сообщает `{"status": "skipped"}`, поэтому ответ всегда содержит один и тот же набор ключей.
`circuit_breakers` сообщает состояние автоматического выключателя (circuit breaker) для выборки артефактов каждого вышестоящего узла (`"open"` или `"closed"`), с ключом по хосту вышестоящего узла — или по заполнителю `hostless-url-<digest>`, описанному в разделе [Monitoring](#monitoring), когда у URL выборки нет хоста для чтения. Ключ опускается, пока прокси не выбрал артефакт хотя бы с одного вышестоящего узла, и хост появляется только после того, как для него был создан выключатель. Выключатели срабатывают после повторных сбоев вышестоящего узла и повторяют попытку обращения к вышестоящему узлу после экспоненциальной задержки. Пока выключатель открыт, загрузки артефактов для этого хоста возвращают HTTP 502 при промахе кэша, не обращаясь к вышестоящему узлу; уже кэшированные артефакты по-прежнему отдаются из хранилища, поскольку кэш проверяется перед загрузчиком. Выключатель сообщается как `"open"` на протяжении всей своей задержки, включая окно полуоткрытого состояния, в котором он допускает один пробный запрос для проверки восстановления. Состояние выключателя является локальным для процесса и хранится в памяти, поэтому перезапуск его очищает, но перезапуск не нужен для восстановления: задержка продолжает повторять попытки, пока выключатель открыт, поэтому он закрывается сам, как только вышестоящий узел снова начинает обслуживать запросы.
Открытый выключатель **не** устанавливает `status` в `"error"` и не меняет код состояния HTTP: он сообщает о конкретном вышестоящем узле, отказывающемся обслуживать запросы, а не о том, что этот прокси непригоден для приёма трафика, и провал проверки готовности из-за одного нездорового вышестоящего узла вывел бы pod из ротации также и для всех остальных экосистем. Используйте `proxy_circuit_breaker_state` для оповещения об этом.
Результаты проверки хранилища кэшируются на `health.storage_probe_interval` (по умолчанию 30 с), чтобы ограничить стоимость проверки удалённых бэкендов. Проверка удерживает внутренний мьютекс до 10 секунд (жёстко заданный тайм-аут на одну проверку), поэтому `/health` предназначен как Kubernetes-проба **готовности** (readiness), а не проба живучести (liveness) — медленный круговой обмен с S3 должен выводить pod из ротации, а не перезапускать его.
Конфигурация сбора метрик для Prometheus:```yaml
scrape_configs:
- job_name: git-pkgs-proxy
static_configs:
- targets: ["localhost:8080"]
Создайте /etc/systemd/system/proxy.service:```ini
[Unit]
Description=git-pkgs proxy
After=network.target
[Service] Type=simple User=proxy ExecStart=/usr/local/bin/proxy -config /etc/proxy/config.yaml Restart=always RestartSec=5
[Install] WantedBy=multi-user.target
Включить и запустить:```bash
sudo systemctl enable proxy
sudo systemctl start proxy
В репозитории есть Dockerfile. Сборка и запуск:```bash docker build -t proxy . docker run -p 8080:8080 -v proxy-data:/data proxy
С Postgres и S3:```bash
docker run -p 8080:8080 \
-e PROXY_DATABASE_DRIVER=postgres \
-e PROXY_DATABASE_URL=postgres://user:pass@db:5432/proxy \
-e PROXY_STORAGE_URL=s3://my-bucket?region=us-east-1 \
-e AWS_ACCESS_KEY_ID=... \
-e AWS_SECRET_ACCESS_KEY=... \
proxy
При работе за nginx, Apache или другим обратным прокси установите base_url на ваш публичный URL:```yaml
base_url: "https://proxy.example.com"
Если UI доступен по другому имени хоста, отличному от конечных точек пакетов — например, UI опубликован публично на домене, а сборочные машины обращаются к псевдониму сети Docker — задайте `ui_base_url` отдельно. `base_url` — это URL, используемый менеджерами пакетов и при перезаписи метаданных; `ui_base_url` — это URL, объявляемый людям, посещающим веб-интерфейс (теги canonical/`og:url` и баннер руководства по установке):```yaml
base_url: "http://pkg-proxy:8080" # internal alias for build machines
ui_base_url: "https://proxy.example.com/ui" # public UI URL
Если не задано, ui_base_url по умолчанию принимает значение base_url.
Предупреждение: прокси обслуживает UI и эндпоинты пакетов на одном и том же слушателе. Установка
ui_base_urlменяет только тот URL, который UI сообщает людям; это не мешает эндпоинтам пакетов оставаться доступными на том же имени хоста и порту. При размещении прокси за публичным обратным прокси ограничьте публичный маршрут доPathPrefix(/ui)(или эквивалента в вашем прокси), иначе/npm,/pypiи другие эндпоинты пакетов останутся открытыми наряду с UI.
Пример nginx, ограничивающий публичный хост только UI, при этом эндпоинты пакетов остаются доступными только на внутреннем слушателе:```nginx server { listen 443 ssl; server_name proxy.example.com;
location /ui/ {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_buffering off;
}
location / {
return 404;
}
}
Пример Traefik с использованием `PathPrefix(/ui)`, чтобы публичный маршрутизатор соответствовал только трафику UI:```yaml
labels:
traefik.enable: "true"
traefik.http.services.pkg-proxy.loadbalancer.server.port: "8080"
traefik.http.routers.pkg-proxy.rule: "Host(`proxy.example.com`) && PathPrefix(`/ui`)"
traefik.http.routers.pkg-proxy.entrypoints: "websecure"
Прокси хранит артефакты в настроенном каталоге хранилища со следующей структурой:``` cache/artifacts/ ├── npm/ │ └── lodash/ │ └── 4.17.21/ │ └── lodash-4.17.21.tgz ├── cargo/ │ └── serde/ │ └── 1.0.193/ │ └── serde-1.0.193.crate ├── oci/ │ └── library/nginx/ │ └── sha256:abc123.../ │ └── sha256:abc123... ├── deb/ │ └── nginx/ │ └── 1.18.0-6/ │ └── nginx_1.18.0-6_amd64.deb └── rpm/ └── nginx/ └── 1.24.0-1.fc39/ └── nginx-1.24.0-1.fc39.x86_64.rpm
Метаданные кэша хранятся в SQLite (по умолчанию) или PostgreSQL. Чтобы очистить локальный кэш:```bash
rm -rf ./cache/artifacts/*
rm ./cache/proxy.db
Прокси пересоздаст базу данных при следующем запуске.
Требования:
go.mod)```bash
git clone https://github.com/git-pkgs/proxy.git
cd proxy
go build -o proxy ./cmd/proxyЗапустите тесты:```bash
go test ./...
GPL-3.0-or-later
| Chef | Chef | ✗ |
| Generic | Any | ✓ |
| Helm | Kubernetes | ✓ |
| Vagrant | Vagrant | ✗ |
| Протокол репозитория Alpine APK |
GET /generic/{name}/* | Универсальный прокси HTTP-загрузок (ресурсы релизов GitHub, mise/aqua) |
GET /debian/* | Протокол репозитория Debian/APT |
GET /rpm/* | Протокол репозитория RPM/Yum |
| gauge |
registry |
| Состояние предохранителя выборки артефактов для каждого upstream-реестра (0 — закрыт, 2 — открыт). Публикуется после срабатывания предохранителя этого реестра. |
proxy_circuit_breaker_trips_total | counter | registry | Срабатывания предохранителя по upstream-реестрам. |