
패키지 레지스트리용 경량 캐싱 프록시.
패키지 레지스트리를 위한 캐싱 프록시입니다. 아티팩트를 로컬에 캐싱하여 패키지 다운로드 속도를 높이고, 대역폭 사용량을 줄이며 안정성을 향상시킵니다.
대부분의 공급망 공격은 속도에 의존합니다. 악성 버전이 게시되면 아무도 눈치채기 전에 자동화된 파이프라인에 의해 몇 분 안에 소비됩니다. 쿨다운 기능은 새로 게시된 버전에 격리 기간을 추가합니다. 활성화하면 프록시는 구성 가능한 임계값을 경과할 때까지 메타데이터 응답에서 해당 버전을 제거합니다.```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` 버전을 게시하면 3일이 지날 때까지 빌드가 `4.17.21`을 계속 사용한다는 의미입니다. 새 릴리스가 손상된 것으로 판명되더라도, 귀하는 노출되지 않았습니다.
해결 순서: 패키지 재정의, 그다음 에코시스템 재정의, 그다음 전역 기본값. 이를 통해 보수적인 기본값을 설정하고 더 빠른 업데이트가 필요한 패키지에 대해 예외를 마련할 수 있습니다. 전체 구성 참조는 [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]
프록시는 아티팩트 바이트를 스캐너에 업로드하지 않습니다. 각 스캐너는 패키지 메타데이터와 단기 서명 URL을 받아 알림을 받으며, 스캐너는 프록시 자체 스토리지에서 바이트를 직접 가져옵니다. 스캐너는 동시에 실행되며, block 모드 스캐너가 허용되지 않음 판정을 가장 먼저 보고하면 즉시 승리하고 나머지는 취소됩니다. 전체 구성 참조와 스캐너 HTTP 계약은 docs/configuration.md를 참조하세요.
| 레지스트리 | 언어/플랫폼 | 쿨다운 | 완료 |
|---|---|---|---|
| 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 | ✗ |
쿨다운은 메타데이터에 게시 타임스탬프가 있어야 합니다. 쿨다운 열에 "예"가 없는 레지스트리는 타임스탬프를 노출하지 않거나 아직 연결되지 않은 것입니다.
* Hex 쿨다운은 프록시가 protobuf 페이로드를 재인코딩하므로 레지스트리 서명 검증을 비활성화해야 합니다(HEX_NO_VERIFY_REPO_ORIGIN=1).
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
기본 차트는 /data 아래에서 SQLite와 파일시스템 아티팩트 스토리지를 사용하여 10 GiB 영구 볼륨으로 백업된 하나의 레플리카를 배포합니다. 인그레스, 외부 데이터베이스 및 오브젝트 스토리지 구성 옵션은 deploy/charts/proxy/values.yaml을 참조하세요.
go build -o proxy ./cmd/proxy
./proxy
./proxy -listen :3000 -base-url https://proxy.example.com
이제 프록시가 실행 중입니다. 패키지 관리자가 이를 사용하도록 구성하세요.
## OpenAPI (Swagger)
이 저장소는 주석이 달린 핸들러로부터 OpenAPI 명세를 생성하기 위해 swaggo를 사용합니다.
명세 생성:```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에서 프로젝트별로 설정하세요.
Gemfile에서 gem 소스를 설정하세요:```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
Homebrew의 JSON API와 아티팩트 도메인을 프록시로 지정하세요:```bash
export HOMEBREW_API_DOMAIN=http://localhost:8080/homebrew
export HOMEBREW_ARTIFACT_DOMAIN=http://localhost:8080
아티팩트 도메인은 /v2/homebrew/core/ 아래의 매니페스트와 bottle blob을 프록시합니다. GHCR 라우팅은 해당 리포지터리로 제한됩니다. 소스 아카이브, cask 애플리케이션 다운로드, 사용자 정의 tap 아티팩트, 레거시 플랫 파일 bottle 미러는 Homebrew의 일반 폴백 URL을 사용합니다. HOMEBREW_ARTIFACT_DOMAIN_NO_FALLBACK을 설정하지 않은 상태로 두어 폴백을 활성화하세요.
오프라인 폴백을 위해 Homebrew JSON API 응답을 보존하려면 cache_metadata를 활성화하거나 PROXY_CACHE_METADATA=true를 설정하세요. Bottle blob과 해당 OCI 매니페스트는 이 설정 없이도 캐시됩니다.
업스트림은 기본적으로 JSON API의 경우 https://formulae.brew.sh/api, 아티팩트의 경우 https://ghcr.io로 설정됩니다. 이 프록시를 다른 프록시에 연결하려면 해당 프록시의 Homebrew 엔드포인트를 업스트림으로 구성하세요:```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을 기본 업스트림으로 사용하며, 기본 업스트림이 not found를 반환할 경우 Gradle 플러그인 마커 메타데이터 및 관련 아티팩트를 위해 Gradle Plugin Portal로 폴백합니다.
동일한 프록시 엔드포인트를 통한 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)
})
Julia를 시작하기 전에 Pkg 서버를 설정하세요:```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
Registry 종속성은 `Package.swift`에서 스코프가 지정된 패키지 식별자를 사용합니다:```swift
dependencies: [
.package(id: "apple.swift-argument-parser", from: "1.2.0")
]
프록시는 의존성 해석과 소스 다운로드를 지원합니다. swift package-registry publish를 사용한 게시는 지원되지 않습니다.
/etc/docker/daemon.json에서 Docker가 프록시를 레지스트리 미러로 사용하도록 구성하세요:```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"
get_global_privilege_escalation_vectors이 함수는 시스템에서 전역 권한 상승 벡터를 검색합니다. 이 함수는 get_global_privilege_escalation_vectors 함수를 사용하여 전역 권한 상승 벡터를 가져옵니다.
def get_global_privilege_escalation_vectors(self):
"""
시스템에서 전역 권한 상승 벡터를 가져옵니다.
Returns:
list: 전역 권한 상승 벡터 목록
"""
return self.global_privilege_escalation_vectors
``````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"
## 감사합니다!
이 프로젝트에 기여하고 개선하는 데 도움을 주신 모든 분들께 감사드립니다.```bash
helm pull oci://localhost:8080/upstream/ghcr/owner/charts/mychart --version 1.0.0 --plain-http
/etc/apt/sources.list.d/proxy.list에서 APT가 프록시를 사용하도록 구성하세요:```
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"
### RPM / Yum / DNF
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
### Alpine / apk
`/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
Repository 인덱스(v2 `APKINDEX.tar.gz` 및 v3 `Packages.adb`), 분리된
서명, 그리고 패키지는 바이트 단위로 변경 없이 제공되므로 apk의 정상적인
서명 검증이 계속 작동합니다. 인덱스는 메타데이터 캐시
(`metadata_ttl`, stale fallback)를 사용하며, `.apk` 패키지는 공유
아티팩트 캐시에 저장되어 업스트림에 연결할 수 없을 때도 사용할 수 있습니다.
다른 미러나 비공개 저장소를 프록시하려면 `upstream.apk` 아래에 명명된
업스트림을 구성하세요(이것은 내장 기본값을 대체하므로, 여전히 원한다면
`alpine`을 다시 추가하세요):```yaml
upstream:
apk:
alpine: "https://dl-cdn.alpinelinux.org/alpine"
private: "https://apk.example.com"
get_network_connectionsget_network_connections 함수는 시스템의 네트워크 연결에 대한 정보를 검색합니다.
def get_network_connections(self):
"""
네트워크 연결에 대한 정보를 검색합니다.
반환값:
list: 네트워크 연결에 대한 정보를 담은 딕셔너리 목록.
"""
try:
connections = []
for conn in psutil.net_connections(kind='inet'):
laddr = f"{conn.laddr.ip}:{conn.laddr.port}" if conn.laddr else "N/A"
raddr = f"{conn.raddr.ip}:{conn.raddr.port}" if conn.raddr else "N/A"
connections.append({
'fd': conn.fd,
'family': str(conn.family),
'type': str(conn.type),
'laddr': laddr,
'raddr': raddr,
'status': conn.status,
'pid': conn.pid
})
return connections
except Exception as e:
self.logger.error(f"네트워크 연결을 가져오는 중 오류 발생: {e}")
return []
get_network_statsget_network_stats 함수는 시스템의 네트워크 통계를 검색합니다.
def get_network_stats(self):
"""
네트워크 통계를 검색합니다.
반환값:
dict: 네트워크 통계를 담은 딕셔너리.
"""
try:
net_io = psutil.net_io_counters()
return {
'bytes_sent': net_io.bytes_sent,
'bytes_recv': net_io.bytes_recv,
'packets_sent': net_io.packets_sent,
'packets_recv': net_io.packets_recv,
'errin': net_io.errin,
'errout': net_io.errout,
'dropin': net_io.dropin,
'dropout': net_io.dropout
}
except Exception as e:
self.logger.error(f"네트워크 통계를 가져오는 중 오류 발생: {e}")
return {}
get_network_interfacesget_network_interfaces 함수는 시스템의 네트워크 인터페이스에 대한 정보를 검색합니다.
def get_network_interfaces(self):
"""
네트워크 인터페이스에 대한 정보를 검색합니다.
반환값:
dict: 네트워크 인터페이스에 대한 정보를 담은 딕셔너리.
"""
try:
interfaces = {}
for name, addrs in psutil.net_if_addrs().items():
interfaces[name] = []
for addr in addrs:
interfaces[name].append({
'family': str(addr.family),
'address': addr.address,
'netmask': addr.netmask,
'broadcast': addr.broadcast
})
return interfaces
except Exception as e:
self.logger.error(f"네트워크 인터페이스를 가져오는 중 오류 발생: {e}")
return {}
get_network_io_countersget_network_io_counters 함수는 시스템의 네트워크 I/O 통계를 검색합니다.
def get_network_io_counters(self):
"""
네트워크 I/O 통계를 검색합니다.
반환값:
dict: 네트워크 I/O 통계를 담은 딕셔너리.
"""
try:
net_io = psutil.net_io_counters(pernic=True)
return {nic: {
'bytes_sent': stats.bytes_sent,
'bytes_recv': stats.bytes_recv,
'packets_sent': stats.packets_sent,
'packets_recv': stats.packets_recv,
'errin': stats.errin,
'errout': stats.errout,
'dropin': stats.dropin,
'dropout': stats.dropout
} for nic, stats in net_io.items()}
except Exception as e:
self.logger.error(f"네트워크 I/O 카운터를 가져오는 중 오류 발생: {e}")
return {}
apk는 각 저장소 줄에 아키텍처와 인덱스 파일 이름을 자체적으로
추가합니다.
### GitHub Releases / mise (aqua 백엔드)
명명된 일반 업스트림을 구성합니다:```yaml
upstream:
generic:
github: "https://github.com"
github-api: "https://api.github.com"
그런 다음 mise의 설정(~/.config/mise/config.toml, mise ≥ 2025.9.3)에서 GitHub URL을 다시 작성합니다:```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 호출이 전혀 필요하지 않습니다. 함대가 GitHub의 익명 속도 제한을 초과하는 경우 `upstream.auth` 아래에 `https://api.github.com`용 베어러 토큰을 추가하세요.
## 구성
프록시는 다음을 통해 구성할 수 있습니다:
1. 명령줄 플래그 (최우선 순위)
2. 환경 변수
3. 구성 파일 (YAML 또는 JSON)
### 명령줄 플래그```
-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
### 구성 파일```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"
모든 업스트림 키, 환경 변수, 기본 URL은 구성 참조를 참조하세요.
구성 파일로 실행:```bash ./proxy -config /etc/proxy/config.yaml
### PostgreSQL
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 또는 모든 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`)를 통해 자격 증명을 설정하세요.
### Google Cloud Storage
프록시는 `gs://` URL 스킴을 사용하여 캐시된 아티팩트를 GCS 버킷에 저장할 수 있습니다.```yaml
storage:
url: "gs://my-bucket-name"
인증은 Application Default Credentials를 사용하므로, 자격 증명을 구성이나 환경에 포함할 필요가 없습니다. 지원되는 소스는 다음 순서대로입니다:
roles/storage.objectAdmin 권한이 있는 Google 서비스 계정에 바인딩합니다. 프록시는 워크로드의 토큰을 자동으로 사용합니다.GOOGLE_APPLICATION_CREDENTIALS 환경 변수.gcloud auth application-default login.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
#### Workload Identity를 사용한 직접 제공(서명된 URL)
`direct_serve: true`가 활성화되면 프록시는 미리 서명된 GCS URL로 HTTP 302 리디렉션을 발급합니다. Workload Identity는 개인 키를 제공하지 않으므로 GCS 백엔드는 [IAM Credentials `signBlob` API](https://docs.cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/signBlob)를 호출합니다. 서비스 계정에 자기 자신에 대한 token-creator 역할을 부여하세요:```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"
프록시 서버를 시작합니다. 명령을 지정하지 않으면 기본 명령입니다.```bash proxy serve [flags] proxy [flags] # same as 'proxy serve'
### mirror
PURL, SBOM 파일 또는 전체 레지스트리에서 캐시를 미리 채웁니다. 오프라인 가용성을 보장하거나 배포 전에 캐시를 예열하는 데 유용합니다.```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]
The mirror 명령은 serve와 동일한 스토리지 및 데이터베이스 플래그를 받습니다. 이미 캐시된 아티팩트는 건너뜁니다.
서버가 실행 중일 때는 mirror API도 사용할 수 있습니다:```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
서버를 실행하지 않고 캐시 통계를 표시합니다.```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)
## API 엔드포인트
### 레지스트리 프로토콜
| 엔드포인트 | 설명 |
|----------|-------------|
| `GET /` | 대시보드 (웹 UI) |
| `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/*` | PyPI simple/JSON API |
| `GET /maven/*` | Maven 저장소 프로토콜 |
| `GET /nuget/*` | NuGet V3 API |
| `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/*` | Homebrew JSON API |
| `GET /v2/*` | OCI/Docker 레지스트리 프로토콜 |
| `GET /v2/homebrew/core/*` | GHCR의 Homebrew core bottle 매니페스트 및 blob |
| `GET /apk/{repository}/*` | Alpine APK 저장소 프로토콜 |
| `GET /generic/{name}/*` | 범용 HTTP 다운로드 프록시 (GitHub 릴리스 자산, mise/aqua) |
| `GET /debian/*` | Debian/APT 저장소 프로토콜 |
| `GET /rpm/*` | RPM/Yum 저장소 프로토콜 |
### 미러 API
| 엔드포인트 | 설명 |
|----------|-------------|
| `POST /api/mirror` | 미러 작업 시작 (JSON 본문에 `purls` 또는 인라인 `sbom`) |
| `GET /api/mirror/{id}` | 작업 상태 및 진행률 조회 |
| `DELETE /api/mirror/{id}` | 실행 중인 작업 취소 |
### 보강 API
프록시는 패키지 메타데이터 보강, 취약점 스캔, 구버전 감지를 위한 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` | 대량 패키지 메타데이터 조회 |
#### 패키지 메타데이터 조회```bash
curl http://localhost:8080/api/package/npm/lodash
Response:```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" }
#### 취약점이 있는 버전 가져오기```bash
curl http://localhost:8080/api/package/npm/lodash/4.17.0
No input content was provided in your message. The "INPUT:" section is empty, so there is no text to translate.
Please paste the actual Markdown content for chunk 147, and I will return the Korean translation with all Markdown structure, code, paths, URLs, and identifiers preserved exactly as required.```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" }
#### 오래된 패키지 확인```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"}
]
}'
Response:```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 } ] }
#### 대량 패키지 조회```bash
curl -X POST http://localhost:8080/api/bulk \
-H "Content-Type: application/json" \
-d '{
"purls": [
"pkg:npm/[email protected]",
"pkg:pypi/[email protected]"
]
}'
Response:```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" } } }
### 통계 응답 (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"
}
## 웹 인터페이스
프록시는 `/ui` 아래에 웹 UI를 제공합니다. 별도의 프런트엔드 빌드는 필요하지 않습니다 -- 템플릿과 에셋은 바이너리에 내장되어 있습니다. `GET /`는 `/ui/`로 리다이렉트됩니다. UI는 자체 접두사 아래에 마운트되므로, 리버스 프록시가 패키지 엔드포인트와는 다른 접근 규칙을 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}`) -- 캐시된 두 버전의 나란히 비교로, 추가, 제거, 변경된 파일을 보여줍니다.
## 모니터링
프록시는 `GET /metrics`에서 Prometheus 메트릭을 노출합니다. 모든 메트릭 이름은 `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` | 업스트림에서 가져오는 데 소요된 시간 |
| `proxy_upstream_errors_total` | counter | `ecosystem`, `error_type` | 업스트림 가져오기 실패 |
| `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` | gauge | `registry` | 업스트림 레지스트리별 아티팩트 가져오기 회로 차단기 상태 (0 closed, 2 open). 해당 레지스트리의 차단기가 트립된 후 게시됩니다. |
| `proxy_circuit_breaker_trips_total` | counter | `registry` | 업스트림 레지스트리별 회로 차단기 트립. |
캐시 크기와 아티팩트 수는 60초마다 갱신됩니다. 회로 차단기 상태는 `/metrics`의 각 스크레이프와 각 `/health` 요청 시 페처에서 읽히므로, `proxy_circuit_breaker_trips_total`은 해당 읽기 사이에 보이는 트립을 계산합니다 — 두 스크레이프 사이에 완전히 열렸다가 복구된 차단기는 계산되지 않습니다. 나머지 메트릭은 각 요청 시 갱신됩니다.
차단기 메트릭은 업스트림 호스트당 하나의 시리즈를 가지지만, 시작 이후 최소 한 번 트립된 적이 있는 호스트에 대해서만 그렇습니다. 차단기는 프록시가 아티팩트를 가져오는 호스트별로 생성되며, 일부 에코시스템의 경우 그 호스트는 구성이 아니라 업스트림 메타데이터에서 나옵니다 (composer는 패키지의 `dist.url`에서, helm은 `index.yaml`의 차트 URL에서 가져옵니다). 따라서 모든 호스트를 게시하면 업스트림 콘텐츠가 프로세스 수명 동안 시리즈 수를 늘릴 수 있습니다. 호스트가 한 번 트립되면 계속 보고하므로, 복구는 시리즈가 사라지는 것이 아니라 0으로의 전환으로 나타납니다. `/health`는 영구 시계열이 아니며 트립 여부와 관계없이 모든 차단기를 나열합니다.
`registry` 레이블은 아티팩트를 가져온 URL의 호스트입니다. 그 URL은 업스트림 메타데이터에서 올 수 있으므로, 항상 호스트를 읽어낼 수 있는 것은 아닙니다 — 예를 들어 파싱에 실패한 서명된 `dist.url` 같은 경우입니다 — 그리고 그러한 차단기는 대신 `hostless-url-<digest>`로 레이블이 지정되며, 여기서 digest는 시작 시 새로 도출된 값을 키로 합니다. `/metrics`와 `/health` 모두 인증이 필요하지 않으므로, 가져오기 URL은 레이블이나 키로 게시되지 않습니다. digest는 프로세스가 실행되는 동안 차단기를 식별하되 그 뒤의 URL을 드러내지 않으며, 선택된 URL이 그것과 매칭되도록 허용하지도 않습니다.
`proxy_circuit_breaker_state == 2`가 몇 분 이상 지속될 때 알림을 설정하세요: 차단기가 열려 있는 동안 해당 업스트림의 아티팩트 다운로드는 모든 캐시 미스에서 HTTP 502로 실패하며, 백오프 간격당 단 하나의 프로브 요청만 업스트림에 도달합니다. 캐시된 아티팩트는 계속 제공되며, 같은 에코시스템의 메타데이터도 마찬가지입니다 (메타데이터는 회로 차단기를 거치지 않습니다). 따라서 설치는 부분적인 업스트림 장애처럼 보이는 방식으로 실패합니다.
### 상태 확인
`/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" 필드가 포함됩니다. 스토리지 실패에는 어떤 프로브 단계가 실패했는지(write, size, read, verify, delete)를 식별하는 "step" 필드도 포함됩니다. 데이터베이스 검사가 실패하면 스토리지 항목은 {"status": "skipped"}를 보고하므로 응답은 항상 동일한 키 집합을 갖습니다.
circuit_breakers는 각 업스트림의 아티팩트 가져오기 서킷 브레이커 상태("open" 또는 "closed")를 업스트림 호스트를 키로 하여 보고합니다. 또는 가져오기 URL에 읽을 호스트가 없는 경우 Monitoring에 설명된 hostless-url-<digest> 플레이스홀더를 키로 사용합니다. 이 키는 프록시가 최소 하나의 업스트림에서 아티팩트를 가져오기 전까지 생략되며, 호스트는 해당 호스트에 대한 브레이커가 생성된 후에만 나타납니다. 브레이커는 반복된 업스트림 실패 후 작동하며 지수 백오프 후에 업스트림을 재시도합니다. 브레이커가 열려 있는 동안 해당 호스트의 아티팩트 다운로드는 캐시 미스 시 업스트림에 접촉하지 않고 HTTP 502를 반환합니다. 이미 캐시된 아티팩트는 캐시가 fetcher보다 먼저 확인되므로 여전히 스토리지에서 제공됩니다. 브레이커는 복구를 테스트하기 위해 하나의 프로브 요청을 허용하는 half-open 기간을 포함하여 백오프 전반에 걸쳐 "open"으로 보고됩니다. 브레이커 상태는 프로세스별이며 메모리에 있으므로 재시작하면 초기화되지만, 복구를 위해 재시작이 필요하지는 않습니다. 브레이커가 열려 있는 동안 백오프가 계속 재시도하므로 업스트림이 다시 서비스되면 스스로 닫힙니다.
열린 브레이커는 status를 "error"로 설정하거나 HTTP 상태 코드를 변경하지 않습니다. 이는 이 프록시가 트래픽을 받기에 부적합하다는 것이 아니라 특정 업스트림이 서비스를 거부하고 있음을 보고하는 것이며, 하나의 비정상 업스트림 때문에 준비 상태 프로브를 실패시키면 다른 모든 에코시스템에 대해서도 파드가 로테이션에서 제외되기 때문입니다. 이에 대한 알림에는 proxy_circuit_breaker_state를 사용하십시오.
스토리지 프로브 결과는 원격 백엔드 프로빙 비용을 제한하기 위해 health.storage_probe_interval(기본값 30초) 동안 캐시됩니다. 프로브는 최대 10초(하드코딩된 프로브별 타임아웃) 동안 내부 뮤텍스를 보유하므로, /health는 라이브니스 프로브가 아니라 Kubernetes 준비 상태(readiness) 프로브로 사용하도록 의도되었습니다. 느린 S3 왕복은 파드를 재시작하는 것이 아니라 로테이션에서 제외해야 합니다.
Prometheus용 스크레이프 구성:```yaml scrape_configs:
## 프로덕션 배포
### Systemd 서비스
`/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
### Docker
저장소에 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은 웹 UI를 방문하는 사람에게 광고되는 URL입니다(정규/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`을 설정하면 UI가 사람에게 광고하는 URL만 변경될 뿐, 동일한 호스트 이름과 포트에서 패키지 엔드포인트에 접근하는 것을 막지는 않습니다. 공용 리버스 프록시로 프록시를 앞에 둘 때는 공용 경로를 `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;
}
}
PathPrefix(/ui)를 사용하여 공용 라우터가 UI 트래픽만 매칭하도록 하는 Traefik 예시:```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 (프로젝트 버전은 `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 | ✗ |