폐쇄망에 conda 채널을 들여놔야 한다. conda-forge 전체는 수 TB 라 통째로 받을 수 없으므로 필요한 플랫폼과 파이썬 버전만 골라 받아야 한다. 어떤 파일까지 받아야 채널로 성립하는지도 불분명하다.
conda 채널은 플랫폼별 하위 디렉터리로 나뉜다. 어느 채널이든 구조가 같다.
<채널 루트>/
noarch/ 반드시 있어야 한다
linux-64/
win-64/
osx-arm64/
channeldata.json 채널 전체 요약 (선택)
각 플랫폼 디렉터리에는 패키지 파일과 색인이 들어간다.
| 파일 | 역할 |
|---|---|
*.tar.bz2 |
구형 패키지 형식 |
*.conda |
신형 패키지 형식. zstd 압축이라 더 작고 빠르다 |
repodata.json |
그 디렉터리의 패키지 메타데이터 목록. conda 가 의존성을 푸는 근거 |
repodata.json.bz2 |
위 파일의 압축본 |
current_repodata.json |
최신 버전만 추린 축약본 |
.tar.bz2 만 받으면 안 된다. .conda 형식으로만 올라온 최신 패키지가 빠지고, repodata.json 이 없으면 conda 가 디렉터리를 채널로 인식하지 못한다. 또 noarch/ 를 빼면 순수 파이썬 패키지가 통째로 사라진다.
repodata.json 을 먼저 받아서 목록을 만든 뒤, 그 목록대로 내려받는 것이 순서다. 디렉터리 HTML 을 긁는 방식은 서버 구성이 바뀌면 깨진다.
import json, urllib.request
CHANNEL = "https://conda.anaconda.org/conda-forge"
SUBDIR = "win-64"
WANT_PY = ("py36", "py37", "py38")
with urllib.request.urlopen(f"{CHANNEL}/{SUBDIR}/repodata.json") as resp:
repo = json.load(resp)
targets = {}
for section in ("packages", "packages.conda"):
for fname, meta in repo.get(section, {}).items():
build = meta.get("build", "")
if any(tag in build for tag in WANT_PY):
targets[fname] = meta
total = sum(m["size"] for m in targets.values())
print(f"{len(targets)} files, {total / 1024**3:.2f} GiB")
repodata.json 은 수백 MB 가 되기도 한다. 노트북 환경에서 통째로 출력하면 표시 한도에 걸리므로 파일로 저장하고 필요한 값만 뽑는다.
버전 판별은 파일 이름이 아니라 build 문자열로 하는 것이 정확하다. 파일 이름에 py38 이 들어가지 않는 패키지가 있다.
받은 목록을 순회하며 저장한다. 이미 있는 파일은 크기를 대조해 건너뛴다.
import os, pathlib, requests
OUT = pathlib.Path("mirror") / SUBDIR
OUT.mkdir(parents=True, exist_ok=True)
session = requests.Session()
session.headers.update({"User-Agent": "conda-mirror/1.0"})
for fname, meta in targets.items():
dest = OUT / fname
if dest.exists() and dest.stat().st_size == meta["size"]:
continue
url = f"{CHANNEL}/{SUBDIR}/{fname}"
with session.get(url, stream=True, timeout=(10, 120)) as r:
r.raise_for_status()
tmp = dest.with_suffix(dest.suffix + ".part")
with open(tmp, "wb") as f:
for chunk in r.iter_content(chunk_size=1 << 20):
f.write(chunk)
tmp.replace(dest)
.part 로 받은 뒤 이름을 바꾸는 것이 중요하다. 중간에 끊긴 파일이 완성본처럼 남으면 다음 실행에서 건너뛰어 버린다.
직접 짜기 전에 기존 도구를 먼저 본다. conda-mirror 는 플랫폼·패키지 이름·라이선스로 걸러 받고 색인까지 만들어 준다. 상용 환경이라면 Nexus 나 Artifactory 의 conda 프록시 저장소를 쓰는 편이 운영이 편하다.
받은 파일을 옮긴 뒤에는 그 자리에서 색인을 다시 만들어야 conda 가 읽는다.
conda install -c conda-forge conda-index
python -m conda_index /srv/conda/mirror
디렉터리 구조는 채널 루트를 가리키고, 그 아래에 noarch/ 와 플랫폼 디렉터리가 있어야 한다. 색인은 각 하위 디렉터리마다 repodata.json 을 만든다.
ls /srv/conda/mirror/win-64/repodata.json
웹 서버로 노출한 뒤 클라이언트에서 지정한다.
conda config --add channels http://mirror.example.local/conda/mirror
conda config --set offline false
conda config --show channels
받은 목록의 의존성이 미러 안에서 모두 풀리는지 확인해야 한다. 파이썬 버전만으로 걸러 받으면 의존하는 C 라이브러리 패키지가 빠져 설치가 실패한다. 반입 전에 격리된 환경에서 실제 설치를 한 번 해 본다.
conda index 는 옛 이름이고 지금은 conda-index 패키지로 분리됐다. 옛 명령이 남아 있는 환경에서는 동작이 다를 수 있다. (확인 필요)
라이선스를 확인한다. 채널을 통째로 복제해 사내 배포하는 행위는 채널마다 약관이 다르다.