
Un proxy ligero de caché para registros de paquetes.
Un proxy con caché para registros de paquetes. Acelera las descargas de paquetes almacenando artefactos en caché localmente, lo que reduce el uso de ancho de banda y mejora la fiabilidad.
La mayoría de los ataques a la cadena de suministro dependen de la velocidad: una versión maliciosa se publica y es consumida por pipelines automatizados en cuestión de minutos, antes de que nadie lo note. La función de enfriamiento añade un periodo de cuarentena a las versiones recién publicadas. Cuando está habilitada, el proxy elimina las versiones de las respuestas de metadatos hasta que hayan superado un umbral configurable.```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
Un período de enfriamiento de 3 días significa que cuando `lodash` publica la versión `4.18.0`, tus compilaciones siguen usando `4.17.21` hasta que hayan pasado 3 días. Si la nueva versión resulta estar comprometida, nunca estuviste expuesto.
Orden de resolución: anulación de paquete, luego anulación de ecosistema, luego valor predeterminado global. Esto te permite establecer un valor predeterminado conservador y crear excepciones para paquetes donde necesitas actualizaciones más rápidas. Consulta [docs/configuration.md](https://github.com/git-pkgs/proxy/blob/main/docs/configuration.md) para la referencia completa de configuración.
## Escaneo de artefactos
El enfriamiento solo examina la marca de tiempo de publicación de una versión — nunca inspecciona los bytes reales. El escaneo de artefactos cierra esa brecha: cuando está habilitado, cada artefacto se almacena temporalmente en el almacenamiento y es escaneado por uno o más servicios externos (trivy, ClamAV, Wiz, o cualquier otra cosa que hable un pequeño contrato HTTP/JSON) antes de ser confirmado en la caché y servido a los clientes.```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]
El proxy nunca sube los bytes de los artefactos a un escáner. Cada escáner es notificado con metadatos del paquete más una URL firmada de corta duración; el escáner descarga los bytes por sí mismo desde el almacenamiento propio del proxy. Los escáneres se ejecutan de forma concurrente, y el primer escáner en modo block que reporte un veredicto de no permitido gana de inmediato, cancelando el resto. Consulta docs/configuration.md para la referencia completa de configuración y el contrato HTTP del escáner.
| Registro | Lenguaje/Plataforma | Enfriamiento | Completado |
|---|---|---|---|
| npm | JavaScript | Sí | ✓ |
| Cargo | Rust | Sí | ✓ |
| RubyGems | Ruby | Sí | ✓ |
| Go proxy | Go | ✓ | |
| Hex | Elixir | Sí* | ✓ |
| pub.dev | Dart | Sí | ✓ |
| PyPI | Python | Sí | ✓ |
| Maven | Java | ✓ | |
| Gradle Build Cache | Java/Kotlin | ✓ | |
| NuGet | .NET | Sí | ✓ |
| Composer | PHP | Sí | ✓ |
| Conan | C/C++ | ✓ | |
| Conda | Python/R | Sí | ✓ |
| CRAN | R | ✓ | |
| Julia | Julia | ✓ | |
| Swift | Swift | ✓ | |
| Container | Docker/OCI | ✓ | |
| Homebrew | macOS/Linux | ✓ | |
| Debian | Debian/Ubuntu | ✓ | |
| RPM | RHEL/Fedora | ✓ | |
| Alpine | Alpine Linux | ✓ | |
| Arch | Arch Linux | ✗ |
El enfriamiento requiere marcas de tiempo de publicación en los metadatos. Los registros sin un "Sí" en la columna de enfriamiento o bien no exponen marcas de tiempo o aún no han sido integrados.
* El enfriamiento de Hex requiere deshabilitar la verificación de firma del registro (HEX_NO_VERIFY_REPO_ORIGIN=1) ya que el proxy recodifica la carga útil del protobuf.
brew install git-pkgs/git-pkgs/proxy
O descarga un binario desde la [página de releases](https://github.com/git-pkgs/proxy/releases).
### Helm
Instala el chart desde GHCR, configurando la URL pública que los clientes de gestores de paquetes usarán para acceder al proxy:```bash
helm install proxy oci://ghcr.io/git-pkgs/charts/proxy \
--set config.data.base_url=https://proxy.example.com
El chart predeterminado despliega una réplica respaldada por un volumen persistente de 10 GiB,
usando SQLite y almacenamiento de artefactos en el sistema de archivos bajo /data. Consulte
deploy/charts/proxy/values.yaml para las opciones de configuración de ingress,
base de datos externa y almacenamiento de objetos.
go build -o proxy ./cmd/proxy
./proxy
./proxy -listen :3000 -base-url https://proxy.example.com
El proxy ya está en ejecución. Configure sus gestores de paquetes para usarlo.
## OpenAPI (Swagger)
Este repositorio utiliza swaggo para generar una especificación OpenAPI a partir de los manejadores anotados.
Genere la especificación:```bash
go install github.com/swaggo/swag/cmd/swag@latest
go generate ./internal/server
Los archivos generados se escriben en docs/swagger/.
Cuando el proxy está en ejecución, obtén la especificación en vivo desde:
http://localhost:8080/openapi.jsonO reemplaza http://localhost:8080 con tu URL base configurada. Este enlace también se muestra en el panel de control.
Crea o edita ~/.npmrc:```
registry=http://localhost:8080/npm/
O configurar por proyecto en `.npmrc`:```
registry=http://localhost:8080/npm/
O utilice la variable de entorno:```bash npm_config_registry=http://localhost:8080/npm/ npm install
### Cargo
Crea o edita `~/.cargo/config.toml`:```toml
[source.crates-io]
replace-with = "proxy"
[source.proxy]
registry = "sparse+http://localhost:8080/cargo/"
O configúralo por proyecto en .cargo/config.toml en la raíz de tu proyecto.
Configura la fuente de gemas en tu Gemfile:```ruby
source "http://localhost:8080/gem"
O configurar globalmente:```bash
gem sources --add http://localhost:8080/gem/
bundle config mirror.https://rubygems.org http://localhost:8080/gem
Establece la variable de entorno GOPROXY:```bash export GOPROXY=http://localhost:8080/go,direct
O en el perfil de tu shell para que sea persistente.
### Homebrew
Apunta la API JSON y el dominio de artefactos de Homebrew al proxy:```bash
export HOMEBREW_API_DOMAIN=http://localhost:8080/homebrew
export HOMEBREW_ARTIFACT_DOMAIN=http://localhost:8080
El dominio de artefactos actúa como proxy de manifiestos y blobs de bottle bajo /v2/homebrew/core/. El enrutamiento de GHCR está limitado a ese repositorio. Los archivos fuente, las descargas de aplicaciones de cask, los artefactos de taps personalizados y los mirrors de bottles de archivo plano heredados utilizan las URLs de fallback normales de Homebrew. Mantén el fallback habilitado dejando HOMEBREW_ARTIFACT_DOMAIN_NO_FALLBACK sin establecer.
Habilita cache_metadata o establece PROXY_CACHE_METADATA=true para conservar las respuestas de la API JSON de Homebrew para el fallback sin conexión. Los blobs de bottle y sus manifiestos OCI se almacenan en caché sin esta configuración.
Los upstreams predeterminados son https://formulae.brew.sh/api para la API JSON y https://ghcr.io para los artefactos. Para encadenar este proxy a otro proxy, configura sus endpoints de Homebrew como los upstreams:```yaml
upstream:
homebrew_api: "https://upstream-proxy.example.com/homebrew"
homebrew_artifact: "https://upstream-proxy.example.com"
Las variables de entorno equivalentes son `PROXY_UPSTREAM_HOMEBREW_API` y `PROXY_UPSTREAM_HOMEBREW_ARTIFACT`.
### Hex (Elixir)
Configurar en `~/.hex/hex.config`:```erlang
{default_url, <<"http://localhost:8080/hex">>}.
O establece la variable de entorno:```bash export HEX_MIRROR=http://localhost:8080/hex
### pub.dev (Dart/Flutter)
Establezca la variable de entorno PUB_HOSTED_URL:```bash
export PUB_HOSTED_URL=http://localhost:8080/pub
Configura pip para usar el proxy:```bash pip install --index-url http://localhost:8080/pypi/simple/ package_name
O configúralo en `~/.pip/pip.conf`:```ini
[global]
index-url = http://localhost:8080/pypi/simple/
Añade a tu ~/.m2/settings.xml:```xml
proxy
central
http://localhost:8080/maven/
El endpoint `/maven/` utiliza Maven Central como upstream principal y recurre al Gradle Plugin Portal para los metadatos de los marcadores de plugins de Gradle y los artefactos relacionados cuando el upstream principal devuelve no encontrado.
Para la resolución de plugins de Gradle a través del mismo endpoint del proxy:```kotlin
pluginManagement {
repositories {
maven(url = "http://localhost:8080/maven/")
}
}
Configurar en settings.gradle(.kts):```kotlin
buildCache {
local {
enabled = false
}
remote {
url = uri("http://localhost:8080/gradle/")
push = true
}
}
### NuGet
Configurar en `nuget.config`:```xml
<configuration>
<packageSources>
<clear />
<add key="proxy" value="http://localhost:8080/nuget/v3/index.json" />
</packageSources>
</configuration>
O usa la CLI:```bash dotnet nuget add source http://localhost:8080/nuget/v3/index.json -n proxy
### Composer (PHP)
Configurar en `composer.json`:```json
{
"repositories": [
{
"type": "composer",
"url": "http://localhost:8080/composer"
}
]
}
O establecer globalmente:```bash composer config -g repositories.proxy composer http://localhost:8080/composer
### Conan (C/C++)
Añade el proxy como un remoto:```bash
conan remote add proxy http://localhost:8080/conan
conan remote disable conancenter
O configurar en ~/.conan2/remotes.json.
Configurar en ~/.condarc:```yaml
channels:
O establecer mediante comando:```bash
conda config --add channels http://localhost:8080/conda/main
Configura el repositorio en R:```r options(repos = c(CRAN = "http://localhost:8080/cran"))
O en `~/.Rprofile` para persistencia:```r
local({
r <- getOption("repos")
r["CRAN"] <- "http://localhost:8080/cran"
options(repos = r)
})
Configure el servidor Pkg antes de iniciar Julia:```bash export JULIA_PKG_SERVER=http://localhost:8080/julia
O dentro de una sesión en ejecución:```julia
ENV["JULIA_PKG_SERVER"] = "http://localhost:8080/julia"
using Pkg; Pkg.update()
Configura el proxy como el registro predeterminado para el paquete Swift actual:```bash swift package-registry set --allow-insecure-http http://localhost:8080/swift
Las dependencias del registro utilizan su identificador de paquete con ámbito en `Package.swift`:```swift
dependencies: [
.package(id: "apple.swift-argument-parser", from: "1.2.0")
]
El proxy admite la resolución de dependencias y las descargas de código fuente. La publicación con
swift package-registry publish no es compatible.
Configure Docker para usar el proxy como espejo de registro en /etc/docker/daemon.json:```json
{
"registry-mirrors": ["http://localhost:8080"]
}
Luego reinicia Docker:```bash
sudo systemctl restart docker
O extraer imágenes directamente:```bash docker pull localhost:8080/library/nginx:latest
### Helm
Configure cada repositorio de gráficos HTTP con un nombre, luego añada la URL del proxy correspondiente a Helm:```yaml
upstream:
helm:
bitnami: "https://charts.bitnami.com/bitnami"
| --no-color | Desactiva la salida con color |
| --debug | Habilita el registro de depuración |
| --verbose | Habilita el registro detallado |
| --silent | Suprime toda la salida excepto los resultados |
| --json | Salida en formato JSON |
| --csv | Salida en formato CSV |
| --html | Salida en formato HTML |
| --markdown | Salida en formato Markdown |
| --output <file> | Escribe la salida en un archivo |
| --config <file> | Ruta al archivo de configuración |
| --threads <n> | Número de hilos a utilizar |
| --timeout <seconds> | Tiempo de espera de la solicitud en segundos |
| --retries <n> | Número de reintentos para solicitudes fallidas |
| --proxy <url> | URL del proxy a utilizar |
| --user-agent <string> | Cadena de User-Agent a utilizar |
| --cookie <string> | Cadena de cookie a enviar |
| --header <header> | Encabezado personalizado a enviar |
| --method <method> | Método HTTP a utilizar |
| --data <data> | Datos a enviar en el cuerpo de la solicitud |
| --follow-redirects | Seguir redirecciones HTTP |
| --max-redirects <n> | Número máximo de redirecciones a seguir |
| --insecure | Deshabilitar la verificación del certificado SSL |
| --rate-limit <n> | Limitar la tasa de solicitudes por segundo |
| --random-agent | Utilizar una cadena de User-Agent aleatoria |
| --tor | Enrutar el tráfico a través de Tor |
| --check-tor | Verificar si Tor está funcionando correctamente |
| --update | Actualizar la herramienta a la última versión |
| --version | Mostrar la versión de la herramienta |
| --help | Mostrar el mensaje de ayuda |```bash
helm repo add bitnami http://localhost:8080/helm/bitnami
helm repo update
helm pull bitnami/nginx
El proxy almacena en caché `index.yaml` utilizando la configuración normal de caché de metadatos y
almacena en caché los archivos de charts tras verificar su digest SHA-256 del índice.
Para charts almacenados en un registro OCI, configure un upstream OCI con nombre y añada
el prefijo reservado `upstream/{name}` a la referencia del chart:```yaml
upstream:
oci:
ghcr: "https://ghcr.io"
-h, --help | Muestra el mensaje de ayuda y sale |
-v, --version | Muestra la versión del programa y sale |
-d, --debug | Habilita el registro de depuración |
-c, --config | Ruta al archivo de configuración |
-o, --output | Ruta del archivo de salida |
-f, --format | Formato de salida (json, yaml, xml) |
-q, --quiet | Modo silencioso, suprime la salida |
-V, --verbose | Modo detallado, aumenta la salida |
| helm pull oci://localhost:8080/upstream/ghcr/owner/charts/mychart --version 1.0.0 --plain-http |
### Debian / APT
Configura APT para usar el proxy en `/etc/apt/sources.list.d/proxy.list`:```
deb http://localhost:8080/debian stable main contrib
Reemplace sus entradas existentes en sources.list, luego:```bash sudo apt update
El upstream por defecto es `http://deb.debian.org/debian`. Para hacer proxy de un repositorio APT diferente (por ejemplo, Ubuntu), configure `upstream.debian` en el archivo de configuración o `PROXY_UPSTREAM_DEBIAN` en el entorno:```yaml
upstream:
debian: "http://archive.ubuntu.com/ubuntu"
Configure yum/dnf para usar el proxy en /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
Luego:```bash
sudo dnf clean all
sudo dnf update
Apunte /etc/apk/repositories al proxy. El nombre de repositorio predeterminado
alpine actúa como proxy del mirror oficial (https://dl-cdn.alpinelinux.org/alpine):```
http://localhost:8080/apk/alpine/v3.22/main
http://localhost:8080/apk/alpine/v3.22/community
Luego:```bash
apk update
Los índices del repositorio (v2 APKINDEX.tar.gz y v3 Packages.adb), las firmas
separadas y los paquetes se sirven byte por byte sin cambios, por lo que la
verificación de firmas normal de apk sigue funcionando. Los índices utilizan la caché de
metadatos (metadata_ttl, respaldo obsoleto); los paquetes .apk se almacenan en la caché
compartida de artefactos y permanecen disponibles cuando el upstream es inaccesible.
Para actuar como proxy de otros mirrors o repositorios privados, configure upstreams
con nombre bajo upstream.apk (esto reemplaza el valor predeterminado integrado; vuelva a agregar alpine si
aún lo desea):```yaml
upstream:
apk:
alpine: "https://dl-cdn.alpinelinux.org/alpine"
private: "https://apk.example.com"
| `--no-color` | Desactiva la salida con color |
| `--verbose` | Habilita el registro detallado |
| `--quiet` | Suprime toda la salida excepto los errores |
| `--config <path>` | Especifica una ruta de archivo de configuración personalizada |
| `--timeout <seconds>` | Establece un tiempo de espera para las operaciones de red |
| `--retry <count>` | Número de reintentos para solicitudes fallidas |
| `--proxy <url>` | Usa el proxy especificado para las conexiones |
| `--output <format>` | Formato de salida (json, yaml, table) |
| `--log-level <level>` | Establece el nivel de registro (debug, info, warn, error) |
### Ejemplos
```bash
# Ejecutar con configuración predeterminada
tool run --target example.com
# Especificar un archivo de configuración personalizado
tool run --config /path/to/config.yaml
# Establecer el nivel de registro en debug
tool run --log-level debug
# Usar proxy para las conexiones
tool run --proxy http://127.0.0.1:8080
# Establecer un tiempo de espera de 30 segundos
tool run --timeout 30
| Variable | Descripción |
|---|---|
TOOL_CONFIG | Ruta al archivo de configuración |
TOOL_LOG_LEVEL | Nivel de registro |
TOOL_PROXY | URL del proxy |
TOOL_TIMEOUT | Tiempo de espera en segundos |
TOOL_API_KEY | Clave de API para servicios remotos |
| Código | Descripción |
|---|---|
0 | Éxito |
1 | Error general |
2 | Argumentos de línea de comandos no válidos |
3 | Error de configuración |
4 | Error de red |
5 | Permiso denegado |
El comando no se encuentra
Asegúrese de que el directorio de instalación esté en su PATH:
export PATH=$PATH:/usr/local/tool/bin
Permiso denegado
Ejecute con privilegios elevados o ajuste los permisos de archivo:
chmod +x /usr/local/tool/bin/tool
Errores de conexión
Verifique la configuración de su proxy y la conectividad de red:
curl -v https://api.example.com/health
Problemas de configuración
Valide su archivo de configuración:
tool validate --config /path/to/config.yaml
apk añade la arquitectura y el nombre del archivo de índice a cada línea de repositorio por sí mismo.
### GitHub Releases / mise (backend de aqua)
Configurar upstreams genéricos con nombre:```yaml
upstream:
generic:
github: "https://github.com"
github-api: "https://api.github.com"
Luego reescribe las URL de GitHub en la configuración de 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"
Los recursos de la versión se almacenan en caché de forma permanente después de la primera descarga y siguen
instalándose mientras GitHub está caído. Las búsquedas de etiquetas a través de `api.github.com` se
almacenan en caché durante `metadata_ttl` y se sirven obsoletas durante una interrupción o límite de tasa.
Confirma un `mise.lock` e instala con `mise install --locked` para que las
instalaciones fijadas no necesiten ninguna llamada a la API. Añade un token de portador para `https://api.github.com`
en `upstream.auth` si el conjunto supera el límite de tasa anónimo de GitHub.
## Configuración
El proxy se puede configurar mediante:
1. Indicadores de línea de comandos (mayor prioridad)
2. Variables de entorno
3. Archivo de configuración (YAML o JSON)
### Indicadores de línea de comandos```
-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
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
### Archivo de Configuración```yaml
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
# Optional: override upstream URLs
upstream:
npm: "https://registry.npmjs.org"
cargo: "https://index.crates.io"
swift: "https://tuist.dev/api/registry/swift"
# Optional: version cooldown (see above)
cooldown:
default: "3d"
Consulta la referencia de configuración para conocer cada clave de upstream, variable de entorno y URL predeterminada.
Ejecutar con archivo de configuración:```bash ./proxy -config /etc/proxy/config.yaml
### PostgreSQL
SQLite es la opción predeterminada y funciona bien para implementaciones de un solo nodo. Para configuraciones de múltiples nodos o si prefieres una base de datos gestionada, cambia a Postgres:```yaml
database:
driver: "postgres"
url: "postgres://user:password@localhost:5432/proxy?sslmode=disable"
O mediante variables de entorno:```bash PROXY_DATABASE_DRIVER=postgres PROXY_DATABASE_URL=postgres://user:password@localhost:5432/proxy?sslmode=disable
El proxy crea las tablas automáticamente en la primera ejecución.
### Almacenamiento S3
El proxy puede almacenar artefactos en caché en S3 o en cualquier servicio compatible con S3 (MinIO, R2, etc.) en lugar del sistema de archivos local.```yaml
storage:
url: "s3://my-bucket-name?region=us-east-1"
Para servicios compatibles con S3 como MinIO:```yaml storage: url: "s3://my-bucket?endpoint=http://localhost:9000&disableSSL=true&s3ForcePathStyle=true"
Configura las credenciales mediante las variables de entorno estándar de AWS (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`).
### Google Cloud Storage
El proxy puede almacenar artefactos en caché en un bucket de GCS utilizando el esquema de URL `gs://`.```yaml
storage:
url: "gs://my-bucket-name"
La autenticación utiliza Application Default Credentials, lo que significa que no es necesario incrustar credenciales en la configuración ni en el entorno. Fuentes compatibles, en orden:
roles/storage.objectAdmin en el bucket. El proxy utilizará el token de la carga de trabajo automáticamente.GOOGLE_APPLICATION_CREDENTIALS que apunta a un archivo de clave JSON de cuenta de servicio.gcloud auth application-default login para desarrollo local.gcloud iam service-accounts create git-pkgs-proxy
--project=PROJECT_ID
gsutil iam ch
serviceAccount:git-pkgs-proxy@PROJECT_ID.iam.gserviceaccount.com:objectAdmin
gs://my-bucket-name
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]"
kubectl annotate serviceaccount KSA_NAME
--namespace=NAMESPACE
iam.gke.io/gcp-service-account=git-pkgs-proxy@PROJECT_ID.iam.gserviceaccount.com
#### Servicio directo (URL firmadas) con Workload Identity
Cuando `direct_serve: true` está habilitado, el proxy emite redirecciones HTTP 302 a URLs de GCS prefirmadas. Workload Identity no proporciona ninguna clave privada, por lo que el backend de GCS llama a la [API `signBlob` de IAM Credentials](https://docs.cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/signBlob). Otorga a la cuenta de servicio el rol de creador de tokens sobre sí misma:```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"
Inicia el servidor proxy. Este es el comando predeterminado si no se especifica ninguno.```bash proxy serve [flags] proxy [flags] # same as 'proxy serve'
### mirror
Precarga la caché desde PURLs, archivos SBOM o registros completos. Útil para garantizar la disponibilidad sin conexión o calentar la caché antes de los despliegues.```bash
# Mirror specific package versions
proxy mirror pkg:npm/[email protected] pkg:cargo/[email protected]
# Mirror all versions of a package
proxy mirror pkg:npm/lodash
# Mirror from a CycloneDX or SPDX SBOM
proxy mirror --sbom sbom.cdx.json
# Preview what would be mirrored
proxy mirror --dry-run pkg:npm/lodash
# Control parallelism
proxy mirror --concurrency 8 pkg:npm/[email protected]
El comando mirror acepta los mismos flags de almacenamiento y base de datos que serve. Los artefactos ya en caché se omiten.
También hay disponible una API de mirror cuando el servidor está en ejecución:```bash
curl -X POST http://localhost:8080/api/mirror
-H "Content-Type: application/json"
-d '{"purls": ["pkg:npm/[email protected]"]}'
curl -X POST http://localhost:8080/api/mirror
-H "Content-Type: application/json"
-d '{"sbom":{"bomFormat":"CycloneDX","components":[{"purl":"pkg:npm/[email protected]"}]}}'
curl -X DELETE http://localhost:8080/api/mirror/mirror-1
### stats
Muestra estadísticas de la caché sin ejecutar el servidor.```bash
# Text output
proxy stats
# JSON output
proxy stats -json
# Custom database path
proxy stats -database-path /var/lib/proxy/cache.db
# With PostgreSQL
proxy stats -database-driver postgres -database-url postgres://user:pass@localhost/proxy
# Show top 20 most popular packages
proxy stats -popular 20
Packages: 45 Versions: 128 Artifacts: 128 Total size: 892.4 MB Total hits: 1547
Packages by ecosystem: npm 32 cargo 13
Most popular packages:
Recently cached: npm/[email protected] (2024-01-15 14:32, 54.2 KB) cargo/[email protected] (2024-01-15 14:28, 412.8 KB)
## Endpoints de API
### Protocolos de Registro
| Endpoint | Descripción |
|----------|-------------|
| `GET /` | Panel de control (interfaz web) |
| `GET /health` | Verificación de estado y estado del circuit breaker upstream (JSON; HTTP 200 saludable, 503 no saludable) |
| `GET /stats` | Estadísticas de caché (JSON) |
| `GET /metrics` | Métricas de Prometheus |
| `GET /npm/*` | Protocolo de registro npm |
| `GET /cargo/*` | Protocolo de índice disperso de Cargo |
| `GET /gem/*` | Protocolo de RubyGems |
| `GET /go/*` | Protocolo de proxy de módulos de Go |
| `GET /hex/*` | Protocolo de Hex.pm |
| `GET /pub/*` | Protocolo de pub.dev |
| `GET /pypi/*` | API simple/JSON de PyPI |
| `GET /maven/*` | Protocolo de repositorio Maven |
| `GET /nuget/*` | API V3 de NuGet |
| `GET /composer/*` | Protocolo de Composer/Packagist |
| `GET /conan/*` | Protocolo de Conan C/C++ |
| `GET /conda/*` | Protocolo de Conda/Anaconda |
| `GET /cran/*` | Protocolo de CRAN (R) |
| `GET /julia/*` | Protocolo de servidor de paquetes de Julia |
| `GET /swift/*` | Protocolo de registro de paquetes de Swift v1 |
| `GET /helm/{repository}/*` | Protocolo de repositorio de charts de Helm HTTP |
| `GET /homebrew/*` | API JSON de Homebrew |
| `GET /v2/*` | Protocolo de registro OCI/Docker |
| `GET /v2/homebrew/core/*` | Manifiestos y blobs de botellas core de Homebrew desde GHCR |
| `GET /apk/{repository}/*` | Protocolo de repositorio APK de Alpine |
| `GET /generic/{name}/*` | Proxy de descarga HTTP genérico (recursos de lanzamiento de GitHub, mise/aqua) |
| `GET /debian/*` | Protocolo de repositorio Debian/APT |
| `GET /rpm/*` | Protocolo de repositorio RPM/Yum |
### API de Espejo
| Endpoint | Descripción |
|----------|-------------|
| `POST /api/mirror` | Iniciar un trabajo de espejo (cuerpo JSON con `purls` o un `sbom` en línea) |
| `GET /api/mirror/{id}` | Obtener el estado y el progreso del trabajo |
| `DELETE /api/mirror/{id}` | Cancelar un trabajo en ejecución |
### API de Enriquecimiento
El proxy proporciona endpoints REST para el enriquecimiento de metadatos de paquetes, el escaneo de vulnerabilidades y la detección de versiones obsoletas.
| Endpoint | Descripción |
|----------|-------------|
| `GET /api/package/{ecosystem}/{name}` | Obtener metadatos del paquete |
| `GET /api/package/{ecosystem}/{name}/{version}` | Obtener metadatos de la versión con vulnerabilidades |
| `GET /api/vulns/{ecosystem}/{name}` | Obtener todas las vulnerabilidades de un paquete |
| `GET /api/vulns/{ecosystem}/{name}/{version}` | Obtener vulnerabilidades de una versión específica |
| `POST /api/outdated` | Verificar múltiples paquetes en busca de versiones obsoletas |
| `POST /api/bulk` | Búsqueda masiva de metadatos de paquetes |
#### Obtener Metadatos del Paquete```bash
curl http://localhost:8080/api/package/npm/lodash
I need the actual content to translate. You've provided the instructions and metadata, but the INPUT section is empty — there's no Markdown text for chunk 143 to translate.
Please paste the source content for this chunk and I'll return the Spanish translation following all the rules above.```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" }
#### Obtener versión con vulnerabilidades```bash
curl http://localhost:8080/api/package/npm/lodash/4.17.0
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" }
#### Comprobar Paquetes Desactualizados```bash
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"}
]
}'
(empty)```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 } ] }
#### Búsqueda masiva de paquetes```bash
curl -X POST http://localhost:8080/api/bulk \
-H "Content-Type: application/json" \
-d '{
"purls": [
"pkg:npm/[email protected]",
"pkg:pypi/[email protected]"
]
}'
(no content provided)```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" } } }
### Respuesta de estadísticas (endpoint HTTP)```json
{
"cached_artifacts": 142,
"total_size_bytes": 523456789,
"total_size": "499.2 MB",
"storage_url": "file:///path/to/cache/artifacts",
"database_path": "./cache/proxy.db"
}
## Interfaz Web
El proxy sirve una interfaz web bajo `/ui`. No se necesita una compilación de frontend separada -- las plantillas y los recursos están integrados en el binario. `GET /` redirige a `/ui/`. La interfaz está montada bajo su propio prefijo para que un proxy inverso pueda aplicar reglas de acceso diferentes a las de los endpoints de paquetes (por ejemplo, requerir autenticación para `PathPrefix(/ui)` mientras se dejan abiertos `/npm`, `/pypi`, etc. para las máquinas de compilación).
- **Panel** (`/ui/`) -- estadísticas de caché, paquetes populares, artefactos almacenados en caché recientemente y resumen de vulnerabilidades.
- **Guía de instalación** (`/ui/install`) -- instrucciones de configuración por ecosistema, para que no tengas que buscarlas aquí.
- **Explorador de paquetes** (`/ui/packages`) -- explora todos los paquetes en caché con filtrado por ecosistema y ordenación por hits, tamaño, nombre o número de vulnerabilidades.
- **Búsqueda** (`/ui/search?q=...`) -- busca paquetes en caché por nombre.
- **Detalle del paquete** (`/ui/package/{ecosystem}/{name}`) -- metadatos, licencia, vulnerabilidades y lista de versiones de un paquete. Puedes seleccionar dos versiones para compararlas.
- **Detalle de la versión** (`/ui/package/{ecosystem}/{name}/{version}`) -- metadatos por versión, hash de integridad, estado de la caché de artefactos y recuentos de hits.
- **Explorador de código fuente** (`/ui/package/{ecosystem}/{name}/{version}/browse`) -- explora los archivos dentro de los archivos comprimidos en caché con resaltado de sintaxis para archivos de texto y vistas previas de imágenes.
- **Diff de versiones** (`/ui/package/{ecosystem}/{name}/compare/{v1}...{v2}`) -- diff lado a lado de dos versiones en caché que muestra los archivos añadidos, eliminados y modificados.
## Monitorización
El proxy expone métricas de Prometheus en `GET /metrics`. Todos los nombres de las métricas llevan el prefijo `proxy_`.
| Métrica | Tipo | Etiquetas | Descripción |
|--------|------|--------|-------------|
| `proxy_requests_total` | counter | `ecosystem`, `status` | Respuestas del proxy por ecosistema de paquetes y estado HTTP |
| `proxy_request_duration_seconds` | histogram | `ecosystem`, `status` | Duración de las peticiones al proxy |
| `proxy_cache_hits_total` | counter | `ecosystem` | Aciertos de caché |
| `proxy_cache_misses_total` | counter | `ecosystem` | Fallos de caché |
| `proxy_cache_size_bytes` | gauge | | Tamaño total de los artefactos en caché |
| `proxy_cached_artifacts_total` | gauge | | Número de artefactos en caché |
| `proxy_upstream_fetch_duration_seconds` | histogram | `ecosystem` | Tiempo empleado en obtener datos del upstream |
| `proxy_upstream_errors_total` | counter | `ecosystem`, `error_type` | Fallos de obtención del upstream |
| `proxy_storage_operation_duration_seconds` | histogram | `operation` | Latencia de lectura/escritura del almacenamiento |
| `proxy_storage_errors_total` | counter | `operation` | Fallos de lectura/escritura del almacenamiento |
| `proxy_active_requests` | gauge | | Peticiones en curso |
| `proxy_health_probe_failures_total` | counter | `step` | Fallos de la sonda de salud del almacenamiento por paso fallido (`write`, `size`, `read`, `verify`, `delete`). |
| `proxy_circuit_breaker_state` | gauge | `registry` | Estado del cortacircuitos de obtención de artefactos por registro upstream (0 cerrado, 2 abierto). Se publica una vez que el cortacircuitos de ese registro ha saltado. |
| `proxy_circuit_breaker_trips_total` | counter | `registry` | Saltos del cortacircuitos por registro upstream. |
El tamaño de la caché y el recuento de artefactos se actualizan cada 60 segundos. El estado del cortacircuitos se lee del fetcher en cada scrape de `/metrics` y en cada petición a `/health`, por lo que `proxy_circuit_breaker_trips_total` cuenta los saltos visibles entre esas lecturas — un cortacircuitos que se abre y se recupera por completo entre dos scrapes no se cuenta. El resto de métricas se actualizan en cada petición.
Las métricas del cortacircuitos llevan una serie por host upstream, pero solo para los hosts cuyo cortacircuitos ha saltado al menos una vez desde el arranque. Se crea un cortacircuitos por cada host del que el proxy obtiene artefactos, y para algunos ecosistemas ese host proviene de los metadatos del upstream en lugar de la configuración (composer lo toma del `dist.url` de un paquete, helm de las URLs de los charts en `index.yaml`), por lo que publicar todos los hosts permitiría que el contenido del upstream hiciera crecer el número de series durante toda la vida del proceso. Una vez que un host ha saltado, sigue reportando, por lo que una recuperación sigue apareciendo como una transición a 0 en lugar de como una serie que desaparece. `/health` no es una serie temporal persistente y lista todos los cortacircuitos, hayan saltado o no.
La etiqueta `registry` es el host de la URL desde la que se obtuvo el artefacto. Como esa URL puede provenir de los metadatos del upstream, no siempre se puede extraer un host de ella — por ejemplo, un `dist.url` firmado que no se puede parsear — y en tal caso el cortacircuitos se etiqueta como `hostless-url-<digest>`, donde el digest se genera a partir de un valor obtenido en el arranque. Ni `/metrics` ni `/health` requieren autenticación, por lo que una URL de obtención nunca se publica como etiqueta ni como clave; el digest identifica el cortacircuitos mientras el proceso esté en ejecución sin revelar la URL que hay detrás ni permitir que una URL elegida se compare con él.
Alerta sobre `proxy_circuit_breaker_state == 2` sostenido durante más de unos minutos: mientras un cortacircuitos está abierto, las descargas de artefactos de ese upstream fallan con HTTP 502 en cada fallo de caché, y solo llega al upstream una única petición de sonda por intervalo de retroceso. Los artefactos en caché siguen sirviéndose, y también los metadatos del mismo ecosistema (los metadatos no pasan por el cortacircuitos), por lo que las instalaciones fallan de una forma que parece una interrupción parcial del upstream.
### Comprobación de Salud
`/health` devuelve un informe JSON estructurado del estado de los subsistemas. HTTP 200 si todas las comprobaciones pasan; 503 si alguna falla.```json
{
"status": "ok",
"checks": {
"database": {"status": "ok"},
"storage": {"status": "ok"}
},
"circuit_breakers": {
"registry.npmjs.org": "closed",
"static.crates.io": "open"
}
}
Las comprobaciones fallidas incluyen un campo "error". Los fallos de almacenamiento también incluyen un campo "step" que identifica qué paso de la sonda falló (write, size, read, verify, delete). Cuando la comprobación de la base de datos falla, la entrada de almacenamiento informa {"status": "skipped"} para que la respuesta siempre contenga el mismo conjunto de claves.
circuit_breakers informa el estado del circuit breaker de obtención de artefactos de cada upstream ("open" o "closed"), indexado por host de upstream — o por el marcador hostless-url-<digest> descrito en Monitoring cuando la URL de obtención no tiene host que leer. La clave se omite hasta que el proxy haya obtenido un artefacto de al menos un upstream, y un host aparece solo una vez que se ha creado un breaker para él. Los breakers se disparan tras fallos repetidos del upstream y reintentan el upstream tras un backoff exponencial. Mientras uno está abierto, las descargas de artefactos para ese host devuelven HTTP 502 en un fallo de caché sin contactar al upstream; los artefactos ya en caché se siguen sirviendo desde el almacenamiento, ya que la caché se comprueba antes que el fetcher. Un breaker se informa como "open" durante todo su backoff, incluida la ventana half-open en la que admite una solicitud de sonda para probar la recuperación. El estado del breaker es por proceso y en memoria, por lo que un reinicio lo borra, pero no se necesita un reinicio para la recuperación: el backoff sigue reintentando mientras el breaker esté abierto, por lo que se cierra por sí solo una vez que el upstream vuelve a servir.
Un breaker abierto no establece status a "error" ni cambia el código de estado HTTP: informa de un upstream específico que se niega a servir, no de que este proxy no sea apto para recibir tráfico, y fallar la sonda de readiness por un upstream no saludable sacaría el pod de rotación también para todos los demás ecosistemas. Use proxy_circuit_breaker_state para alertar sobre ello.
Los resultados de la sonda de almacenamiento se almacenan en caché durante health.storage_probe_interval (por defecto 30s) para acotar el coste de sondear backends remotos. Una sonda mantiene un mutex interno durante hasta 10 segundos (el timeout por sonda codificado de forma fija), por lo que /health está pensado como una sonda de readiness de Kubernetes en lugar de una sonda de liveness — un ida y vuelta lento a S3 debería sacar el pod de rotación, no reiniciarlo.
Configuración de scrape para Prometheus:```yaml scrape_configs:
## Despliegue en Producción
### Servicio Systemd
Crea `/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
Habilitar e iniciar:```bash sudo systemctl enable proxy sudo systemctl start proxy
### Docker
Se incluye un Dockerfile en el repositorio. Compilar y ejecutar:```bash
docker build -t proxy .
docker run -p 8080:8080 -v proxy-data:/data proxy
Con Postgres y 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
### Detrás de un Proxy Inverso
Cuando se ejecuta detrás de nginx, Apache u otro proxy inverso, configure `base_url` con su URL pública:```yaml
base_url: "https://proxy.example.com"
Si se accede a la interfaz de usuario a través de un nombre de host diferente al de los endpoints del paquete —por ejemplo, la interfaz de usuario expuesta públicamente en un dominio mientras las máquinas de compilación acceden a un alias de red de Docker—, configure ui_base_url por separado. base_url es la URL que utilizan los gestores de paquetes y la reescritura de metadatos; ui_base_url es la URL que se anuncia a las personas que visitan la interfaz web (etiquetas canonical/og:url y el banner de la guía de instalación):```yaml
base_url: "http://pkg-proxy:8080" # internal alias for build machines
ui_base_url: "https://proxy.example.com/ui" # public UI URL
Cuando no se establece, `ui_base_url` toma por defecto el valor de `base_url`.
> **Advertencia:** el proxy sirve la UI y los endpoints de paquetes en el mismo listener. Establecer `ui_base_url` solo cambia la URL que la UI anuncia a los humanos; no impide que los endpoints de paquetes sean accesibles en el mismo hostname y puerto. Al poner el proxy detrás de un reverse proxy público, restringe la ruta pública a `PathPrefix(/ui)` (o el equivalente de tu proxy), de lo contrario `/npm`, `/pypi` y los demás endpoints de paquetes quedan expuestos junto con la UI.
Ejemplo de nginx, restringiendo el host público a la UI mientras se dejan los endpoints de paquetes accesibles solo en el listener interno:```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;
}
}
Ejemplo de Traefik usando PathPrefix(/ui) para que el router público solo coincida con el tráfico de la 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"
## Gestión de Caché
El proxy almacena los artefactos en el directorio de almacenamiento configurado con esta estructura:```
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
Los metadatos de la caché se almacenan en SQLite (por defecto) o PostgreSQL. Para borrar una caché local:```bash rm -rf ./cache/artifacts/* rm ./cache/proxy.db
El proxy recreará la base de datos en el próximo inicio.
## Compilar desde el código fuente
Requisitos:
- Go (la versión del proyecto se declara en `go.mod`)```bash
git clone https://github.com/git-pkgs/proxy.git
cd proxy
go build -o proxy ./cmd/proxy
Ejecutar pruebas:```bash go test ./...
## Licencia
GPL-3.0-or-later
| Chef | Chef | ✗ |
| Generic | Any | ✓ |
| Helm | Kubernetes | ✓ |
| Vagrant | Vagrant | ✗ |