A Nix store is three functions
Nix 스토어는 함수 세 개면 충분하다 — GitHub Pages부터 npm까지 바이너리 캐시 만들기
Nix 바이너리 캐시가 되기 위해 필요한 것은 nix-cache-info, narinfo, 압축 아카이브를 내려주는 GET 요청 세 개뿐이라는 글입니다. 서명이 전송 경로가 아니라 콘텐츠 해시를 검증하기 때문에 GitHub Pages, DNS, npm 같은 임의의 정적 매체도 substituter로 쓸 수 있음을 보여주며, 실제로 npm 레지스트리에 Nix 클로저를 발행해 실행까지 시연합니다.
- 주제
본문
저자 Farid Zakaria는 브라우저에서 Nix를 체험하게 해주는 프로젝트 trynix를 만들다가 cache.nixos.org에 없는 store path를 호스팅할 곳이 필요했습니다. 요구 조건은 JavaScript fetch가 가능할 만큼 느슨한 CORS 정책, 즉 access-control-allow-origin: * 헤더뿐이었는데, GitHub Pages가 모든 파일에 이 헤더를 붙여준다는 사실을 발견합니다. 그래서 nix copy --to file://의 출력물을 그대로 Git 저장소에 커밋해 무료 Nix substituter를 만들었고, 이미 tomberek의 github-store가 GitHub release 에셋을 이용해 같은 일을 하고 있었다는 선행 사례도 소개합니다. github-store는 release가 플랫 네임스페이스라 narinfo의 URL 필드에서 nar/ 접두사를 제거한 점이 특징이며, 글에는 glibc-2.38-44의 실제 narinfo 응답(StorePath, URL, Compression, FileHash, FileSize, NarHash, NarSize, References, Deriver, Sig)이 예시로 실려 있습니다.
인터페이스는 함수 세 개
Nix 바이너리 캐시가 구현해야 하는 것은 세 가지뿐입니다. GET nix-cache-info가 StoreDir: /nix/store를 반환하고, GET <32자 해시>.narinfo가 아카이브를 가리키는 메타데이터를 반환하며, narinfo의 URL 필드가 가리키는 곳에서 압축 아카이브를 내려받는 것입니다. 매체는 무엇이든 상관없고 HTTP가 CppNix에 기본 포함된 가장 흔한 방식이며, 원하면 Nix 플러그인으로 새 프로토콜을 구현할 수도 있습니다. 세 요청이 같은 매체나 도메인에 있을 필요조차 없습니다.
서명은 어떻게 되나
전송 경로를 마음대로 바꿔도 되는 이유는 Nix가 애초에 전송을 신뢰하지 않기 때문입니다. narinfo의 Sig 필드는 StorePath, NarHash, NarSize, References만 커버하고 URL, FileHash, FileSize, Compression은 커버하지 않습니다. 아카이브를 받은 뒤 Nix는 압축을 풀어 NarHash가 일치하는지 검사합니다. 이 덕분에 cache.nixos.org가 서명한 패키지를 다른 바이너리 캐시를 경유해 받아도 서명이 그대로 유효하며, URL 필드는 다른 호스트·다른 프로토콜이어도 무방합니다.
대안 스토어들
저자는 HTTP 프록시로 세 함수를 임의 매체에 번역하는 사례들을 조사했습니다. gachix는 아카이브를 git 오브젝트 데이터베이스에 넣어 content-addressing과 델타 압축 덕에 스토어가 스스로 중복을 제거하며, 동등한 일반 캐시 대비 약 82% 작다고 보고됩니다. 저자 자신의 DNS 개념증명은 narinfo와 아카이브의 4KiB 조각을 TXT 레코드에 담고, pastebin은 만료 정책이 자연스러운 가비지 컬렉터 역할을 합니다. 이 외에 OCI 레지스트리를 쓰는 nixcache-oci, 데이터를 영상에 인코딩해 YouTube에 올리는 infinite storage glitch도 언급됩니다.
npm을 캐시로 쓰기
nix copy --to file://가 디렉터리를 만들고 npm이 디렉터리를 발행하므로 둘은 궁합이 좋습니다. 저자는 hello를 overrideAttrs로 패치해 인사말을 'Hello from the npm registry!'로 바꾸고(업스트림 테스트가 원래 문구를 grep하므로 doCheck = false), glibc에 동적 링크된 다섯 개 경로 약 36 MiB짜리 클로저를 자체 키로 서명해 로컬 캐시에 복사한 뒤 package.json만 추가해 npm publish했습니다. 그 결과 @fzakaria/hello-nix-cache가 실제 공개 npm 패키지가 됐고, nix copy --from https://unpkg.com/@fzakaria/hello-nix-cache@latest/에 --trusted-public-keys를 지정하면 Nix가 npm에서 클로저 전체를 받아옵니다. chroot 스토어라 경로가 /nix/store 아래에 남기 때문에 bwrap으로 마운트해 실행하면 바뀐 인사말이 출력됩니다. npm dist-tag로 latest(가변)·버전(불변)을 두어 Nix 채널 비슷한 릴리스 관리도 가능하지만, 증분 발행이 없어 매 버전이 통째 tarball이라 glibc를 공유하는 클로저 50개면 glibc를 50번 올려야 하는 것이 단점입니다. store path별 개별 패키지 + 인덱스 패키지로 해결할 수 있으나 npm 생태계에 성의가 없다며 저자는 만들지 않았습니다.
원문: fzakaria.com / 번역·요약: Trawling