순수 함수형 패키지 매니저 닉스Nix (스케치 중)

Posted on December 12, 2024

뒤로 미뤄 두던 Nix를, Reflex를 공부하면서 결국 만져야 되는 상황이 왔습니다. 국내에도 쓰는 분들이 점점 늘어나는 추세인듯 하나, 한글 자료는 많지 않습니다. 아래는 전혀 완성되지 않은 글입니다. 아직 필요한 부분들을 확인하고 있는 중입니다. 아래는 순수하게 개인이 볼 노트인 상태로, 아직 다른 분들을 위해 정리하지 않았습니다. 주의해서 보세요.


닉스 패키지 매니저 설치부터 까다롭다. obelisk를 설치하며 nix가 어찌 저찌 설치된 것 같은데, nixpkgs를 못찾는다 해서 다시 multi-user 방식으로 설치했다.

https://nixos.org/manual/nix/stable/#transparent-sourcebinary-deployment

패키지를 하스켈같은 순수 함수형 프로그래밍 언어에서의 값value처럼 취급한다. side-effect가 없는 함수로 빌드하고, 한 번 빌드된 이후에는 절대 변하지 않는다. Nix는 패키지를 Nix store(보통 /nix/store)에 저장한다. 각 패키지는 자신만의 고유 서브 디렉토리를 갖는다.

/nix/store/b6gvzjyb2pg0kjfwrjmg1vfhh54ad73z-firefox-33.1/

패키지명 앞의 문자열은 모든 의존성 정보를 갖고 있는 해시값

전역 위치에 패키지를 설치하면, 개발하고 있는 앱에 이 패키지 의존성을 지정하지 않아도 개발자 머신에선 잘 돌아가고, 나중에 사용자는 안 돌아가는 사태가 발생한다. 이를 막기 위해 Nix는 패키지를 글로벌하게 설치하지 않는다.

Garbage collection

λ> nix-env --uninstall firefox

설치 제거를 하면 바로 삭제하지 않는다. 롤백을 원할 수도 있고, 다른 사용자의 프로필에 있는 패키지일 수도 있다.

사용하지 않는 패키지를 삭제하려면 Garbage collector를 돌린다.

λ> nix-collect-garbage

함수형 패키지 언어

Nix라고 부르는 것인지, Nix expression이라 부르는지 헛갈린다.

참조 투명한 소스/바이너리 배치deployment

Nix expression은 소스에서 어떻게 빌드할지 설명한다.

λ> nix-env --install --attr nixpkgs.firefox

(Nix store에 없다면 C라이브러와 컴파일러까지도 설치한다.) source deployment model이다. 가급적 소스에서 빌드하는 건 피하고, binary cache, pre-built 바이너리를 제공하는 웹서버들을 이용한다. 만일 /nix/store/b5lksdklsjfd…-firefox-33.1 을 빌드하라고 하면, 바로 소스에서 빌드하는 게 아니라, 우선 https://cache.nixos.org/b5lksdklsjfd…narinfo 파일이 있는지 체크하고, 있다면 pre-built 패키지를 가져오고, 없으면 빌드 단계로 들어간다.

Nix Packages collection

수백 개의 유닉스 패키지를 포함한 Nix expression 많은 양의 Nix expression을 제공한다.

nix.conf

보통 /etc/nix/nix.conf 에서 찾을 수 있다.
덮어 쓸 패키지들을 지정할 때는 ~/.config/nixpkgs/config.nix를 쓴다.
환경 변수 NIX_CONF_DIR로 전역 설정 파일을 바꿀 수 있다?
XDG_CONFIG_HOME에서 사용자 설정 파일을 찾는다.
대부분 시스템이
XDG_CONFIG_DIR/etc/xdg
XDG_CONFIG_HOME$HOME/.config

Nigpkgs 버전은 nix-channels 옵션으로 지정한다.
설정 파일이 아니라, 환경 변수 NIX_CONFIG에 바로 설정을 넣어 놓을 수도 있는 것 같다.
설정 파일은 한 줄에 name = value 하나 형태다.

빌드 환경 관리

λ> nix-shell '<nixpkgs>' --attr pan

위 명령어를 입력하면 nix shell 로 들어간다.

[nix-shell]$ unpackPhase
[nix-shell]$ cd pan-*
[nix-shell]$ configurePhase
[nix-shell]$ buildPhase 
[nix-shell]$ ./pan/gui/pan

이식성portability

리눅스와 macOS에서 돌아간다.

NixOS

Nix에 기반을 둔 리눅스 배포판이다. 패키지 매니징에만 Nix를 쓰는 게 아니라, 시스템 설정에도 쓴다. (ex. /etc 에 있는 설정 파일들 빌드할 때) Nix로 시스템을 관리하면, 시스템 자체를 이전 설정 상태로 편하게 롤백할 수 있다.

https://nix.dev/tutorials/first-steps/ad-hoc-shell-environments # First steps ## Ad hoc 쉘 환경

λ> nix-shell -p 앱이름

닉스 환경으로 들어가며, 그 환경에만 앱을 설치한다. Ctrl-d로 빠져 나오면 앱을 설치하지 않은 상태가 된다.

닉스쉘로 들어가지 않고, 현재 설치되어 있지 않은데, 간단히 실행해보려면

λ nix-shell -p cowsay --run "cowsay Nix"

--pure 기존 시스템에 있는 환경을 최대한 안쓰게 할 때 쓰는 것 같다. -I 닉스패키지 지정

nix-collect-garbage 임시 닉스쉘에서 사용했던 패키지들 제거

재현 가능한 스크립트

닉스쉘을 shebang 인터프리터로 쓰기

#!/usr/bin/env nix-shell
#! nix-shell -i bash --pure 파일의 나머지를 해석할 인터프리터
#! nix-shell -p bash cacert curl jq python3Packages.xmljson 인터프리터 환경에서 제공하는 패키지 목록
#! nix-shell -I nixpkgs=https://github.com/NixOS/nixpkgs/archive/2a601aafdc5605a5133a2ca506a34a3a73377247.tar.gz
# -I 패키지 위치 명시적으로 지정

선언적인 쉘 환경

환경이 활성화되면, 자동으로 bash 명령어 실행 자동으로 환경 변수 지정 버전 컨트롤에 환경 정의를 넣고, 다른 장비에서 불러서 적용한다. shell.nix파일을 만든다. 대충 apt install ... 등을 쉘에서 단발적으로 실행하지 않고, 쉘 스크립트 파일에 모두 모아 놓는 거랑 비슷하다.

let
  # 명시적인 버전 지정
  nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/nixos-23.11";
  # config, overlays를 이런식으로라도 지정해 놓으면, 전역값이 덮어 씌우거나 할 수 없다.
  pkgs = import nixpkgs { config = {}; overlays = []; };
in

pkgs.mkShell { # 쉘 환경을 만들어내는 함수
  packages = with pkgs; [ # attribute라 부른다.
    cowsay
    lolcat
  ];
  GREETING = "Hello, Nix!";

  # 시작
  shellHook = '' 
    echo $GREETING | cowsay | lolcat
  '';
}

mkShell의 예시 중에, 패키지를 buildInputs나 nativeBuildInputs attribute에 추가하는 것도 있다. 닉스쉘은 원래 “패키지 빌드 디버깅에 필요한 도구”를 가지고 있는 쉘 환경을 만들기 위해 나왔다. 처음 목적은 그랬지만, 지금은 임시 환경을 만드는 용도로도 사용한다. nix-shell을 실행하면, 현재 폴더에서 shell.nix 파일을 찾는다. packages에 써 놓은 것들을 $PATH에서 보이게 해 둔다. 닉스쉘을 켜둔 상태에서 shell.nix 파일을 수정해도 바로 반영되진 않는 것 같다. 닉스쉘을 껐다 켜면 반영된다.

쉘 환경에 들어가기 직전에 실행하고 싶은 것들은 shellHook을 쓴다.

재현성을 높이기 위해 Nixpkgs 고정pinning

{ pkgs ? import <nixpkgs> {}
}:

닉스 패키지를 불러와서, 닉스 표현식을 실행하는 편리한 방법

However, the resulting Nix expression is not fully reproducible

아직 확실하진 않지만, 결과가 항상 같은 건 아닌가 보다. 완벽하게 재현가능한 닉스 표현식을 만들려면, Nixpkgs의 버전을 고정해야 한다.

import는 예약어가 아니라 보통의 함수다

좀 특이한데, import <nixpkgs> 이 자체가 하나의 함수고, 이 함수에 {...} 인자를 넘기는 모양이다. 왜 이렇게 동작하냐면, import는 지정한 파일에서 닉스 표현식을 읽어와 반환한다. 이 반환값이 함수인 경우엔 보통의 함수처럼 인자를 취하는 모양이 된다.

pkgs = import nixpkgs { config = {}; overlays = []; };

import nixpkgs 함수는 인자로 { config, overlays } 속성 집합을 받고 있다.

pkgs.mkShell { 

mkShell도 마찬가지다. 함수가 { ... } 인자를 받는 모양이다.

{ x, y }: x + y 속성 x, y를 가지고 있는 집합을 인자로 받는 람다 함수다.

사용자와 상호 작용interactive해서 평가

Lazy 평가 전략을 취한다. WHNF에 머물지 않고, 모두 평가된 걸로 보려면, repl에서 :p를 붙인다. runHaskell처럼 닉스 파일을 바로 실행하는 방법도 있다. nix-instantiate --eval file.nix 닉스 파일을 지정하지 않으면 default.nix파일을 읽는다.

공백

렉시컬 토큰을 구분하는 구분자. 인덴트나 줄바꿈은 따로 의미를 가지지 않는다.

이름name과 값value

Value는 닉스 언어의 프리미티브 데이터 타입, 리스트, 속성 집합, 함수가 될 수 있다.

재귀 속성 집합Recursive attribute set

rec{ ...}

속성 접근

Attribute Set 이 튜플인 것 같다.

let
  attrset = { x = 1; };
in
attrset.x
let
  attrset = { a = { b = { c = 1;};};};
in
attrset.a.b.c

익숙하지 않은 모양이다. 아래와 같이 읽어 보자.

attrset = { a = <thumb> };
                { b = <thumb> };
                      { c = <thumb> };
                               1

with

let
  a = {
    x = 1;
    y = 2;
    z = 3;
  };
in
with a; [x,y,z] # [ a.x, a.y, a.z]

with의 범위는 다음 다음 세미 콜론까지 - 이 것도 그다지 좋은 컨벤션을 도입한 것 같지 않다.

inherit

let
  x = 1;
  y = 2;
in
{
  inherit x y; # x = x; y = y;
}
{
  inherit (a) x y; # a.x = x; a.y = y;
}

같은 이름 쓰는 걸 줄여준다는데, 실제 실용 소스를 봐야 명확하겠다.

문자열 interpolation

let
  name = "Nix";
in
"hello ${name}"

https://nix.dev/manual/nix/2.18/language/derivations https://nix.dev/

닉스로 소프트웨어 패키징하기 Nix Package manager

{ 
  lib,
  stdenv,
  fetchzip,
}: # 람다 함수의 인자를 구분했던 콜론 

stdenv.mkDerivation {
  pname = "hello";
  version = "2.12.1";

  src = fetchzip {
    url = "https://ftp.gnu.org/gnu/hello/hello-2.12.1.tar.gz";
    sha256 = lib.fakeSha256;
  };
}
let
  nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/nixos-22.11";
  pkgs = import nixpkgs { config = {}; overlays = []; };
in
{
  hello = pkgs.callPackage ./hello.nix { }; # pkgs의 속성을 뒤 이은 함수에게 자동으로 넘긴다.
}

Nix building instructions
“마치 기계한테 이 설명서 따라해서 빌드해 줘” 라고 할 때, 설명서를 derivation이라 한다. Nix(패키지 매니저)가 따라해서 빌드한 후 결과물이 생기는 걸 realised라 한다.

Derivation은 기계가 읽을 수 있는 빌드를 위한 레시피를 가지고 있는 특별한 파일. 아래는 hello world를 출력하는 c를 빌드하는 방법을 적어 놓은 닉스 파일이다.

Derive([("out","/nix/store/1nq46fyv3629slgxnagqn2c01skp7xrq-hello-world","","")]
      ,[("/nix/store/60xqp516mkfhf31n6ycyvxppcknb2dwr-build-hello.drv",["out"])]
      ,["/nix/store/wiviq2xyz0ylhl0qcgfgl9221nkvvxfj-hello.c"]
      ,"x86_64-linux"
      ,"/nix/store/r5lh8zg768swlm9hxxfrf9j8gwyadi72-build-hello"
      ,[]
      ,[("builder","/nix/store/r5lh8zg768swlm9hxxfrf9j8gwyadi72-build-hello")
       ,("name","hello-world")
       ,("out","/nix/store/1nq46fyv3629slgxnagqn2c01skp7xrq-hello-world")
       ,("src","/nix/store/wiviq2xyz0ylhl0qcgfgl9221nkvvxfj-hello.c")
       ,("system","x86_64-linux")
       ]
      )

해시 코드가 길게 있어 복잡하지만, 소스 path와 빌드 후 출력할 path, 빌드 스크립트, 메타데이터(프로젝트 이름, 플랫폼)를 가지고 있다. 필요한 파일들은 /nix/store 에 다 때려 넣고, 독립된 샌드박스에서 빌드한다. 당연히 위 복잡한 코드를 손으로 만들진 않는다.

{ pkgs ? import <nixpkgs> {} }:

derivation {
  name = "hello-world";
  builder = pkgs.writeShellScript "build-hello" ''
    ${pkgs.coreutils}/bin/mkdir -p $out/bin
    ${pkgs.gcc}/bin/gcc $src -o $out/bin/hello -O2
  '';
  src = ./hello.c;
  system = builtins.currentSystem;
}

https://nixos.org/manual/nix/stable/language/derivations.html

import 로드하고 파일에 있는 “닉스 표현식을 반환”한다. <nixpkgs> 닉스 파일 검색 경로. $NIX_PATH 환경 변수로 지정할 수 있다.

전체가 아래같이 생긴 하나의 함수다.

{ 인자 } : 
dervibation { ... }

import <nixpkgs>란 함수에 {}인자를 넘겨 평가한 결과값을 매개 변수 pkgs에 디폴트 값으로 바인딩해서 아래 함수 본문을 실행한다고 읽을 수 있겠다. 위 구문 전체가 하나의 함수니 어딘가에서 “호출”이라는 절차가 있어야 할 것 같은데…

derivation은 가장 중요한 빌트인 함수다. single derivation을 기술하기 위해 쓴다. 입력에서 derive 해서 출력을 만든다는 의미인가?

name (String) derivation의 심볼릭 이름. 대응하는 store derivationstore path에 추가되고, ouput paths에도 추가 된다?

system (String) 빌더 실행체executable?의 시스템 타입. builtins.currentSystem을 평가해서 현재 시스템 타입을 가져올 수 있다.

builder (Path | String) 빌드를 수행할 실행체 경로 nix writeShellScript nixstore에저장할파일명 '' 파일 내용 '' ''이 파일 내용을 쿼트하는데 쓰인다. 특이한 걸 골랐네.

args (List of String) builder 실행체에 넘길 인자

outputs (List of String) Symbolic outputs of the derivation 자꾸 심볼릭이 나온다. 본 내용이 아니라, 어딘가에 빌드 결과물을 넣어 두고, 이에 대한 심볼릭 링크를 거는 것일까? nix outputs = [ "lib" "dev" "doc"] 이렇게 잡아 두면, 예를 들어 Audoconf-style 패키지라면, 빌더는 아래 동작을 한다. default ./configure \ --libdir=$lib/lib \ --includedir=$dev/include \ --docdir=$doc/share/doc name이 있으면, nix derivation { name = "example"; outputs = [ "lib" "dev" "doc" "out" ]; } default /nix/store/<hash>-example-lib /nix/store/<hash>-example-dev /nix/store/<hash>-example-doc /nix/store/<hash>-example

Nix Store에서 쓰이는 파일 시스템은 OS의 파일 시스템과 다르다. 파일 시스템의 추상으로 파일 시스템 오브젝트(File, Directory, Symbolic Link)로 이루어진 간단한 모델을 쓴다. 하드 링크나 소유 권한, 날짜 등의 메타 정보가 없다. 파일들이 가진 메타 정보는 크기와 executable = true | false만 있다.

nix-shell

nix shell과 구별해야 한다.
지정한 derivation의 의존성을 빌드한다. but not the derivation itself 무슨 말이지?
필요한 것들이 준비된 쉘 환경을 제공하는 것 같다. derivation의 목적인 특정 앱을 빌드하는 게 아니라, 앱 빌드를 위한 환경만 빌드한다는 뜻 같다.

패키지는 derivation으로 평가될? 닉스 언어의 함수다.

nix-build

릴리즈 빌드를 한다면 최고의 툴이지만, incremental 빌드 시스템이 아니라서, 개발할 때는 좋지 않다. 한 곳만 변경해도 전체를 다시 재컴파일 해야 한다.

출력되는 메시지를 보면, configure가 호출되고, Makefile을 만들어낸다. stdenv는 GNU Autoconf(자동으로 프로젝트 디렉토리 구조를 파악한다)를 기반으로 빌드한다.

Derivation 만들기

https://nix.dev/tutorials/packaging-existing-software

# hello.nix
{
  stdenv,
  fetchzip,
}: # 인자 두 개를 받는 함수란 뜻이다.

stdenv.mkDerivation {
  pname = "hello";
  version = "2.12.1";

  src = fetchzip {
    url = "https://ftp.gnu.org/gnu/hello/hello-2.12.1.tar.gz";
    sha256 = "";
  };
}
# default.nix
let
  nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/nixos-22.11";
  pkgs = import nixpkgs { config = {}; overlays = []; };
in
{
  hello = pkgs.callPackage ./hello.nix { };
}

callPackage가 hello.nix 파일이 돌려주는 “함수”가 필요로 하는 인자값 stdenv, fetchzip을 넣어 준다.

… 요약하면, 대충 위에처럼 놓고 nix-build를 돌리면 빌드 에러가 나는 이유들 (보통은 라이브러리가 없어서 나는 것 같다.)을 알려준다. 라이브러리가 없다면, http://search.nixos.org/packages 에서 찾던가, nix-locate를 이용해서 찾아서 매개 변수를 만들고, buildInputs = [ 필요한라이브러리1 필요한라이브러리2 ...] 이렇게 써주고, make install이 없다면 installPhase를 만들어 설치 방법을 적어주고, 다시 nix-build를 돌려, 최종 성공하면 result란 폴더가 생긴다. 이 result는 닉스 store의 특정 버전의 심볼릭 링크다.

nix-env

사용자 레벨에서 패키지를 설치할 수 있다. nix-env로 설치하면, 해당 사용자만 쓸 수 있다.

패키지 검색 nix search nixpkgs packagename 패키지 설치 nix-env -iA packagename 설치 목록 nix-env -q 패키지 제거 nix-env -e packagename 패키지 업데이트 nix-env -u

nix-overlay

nixpkgs에 사용자 정의 패키지를 추가하는 방법 Overlay 폴더를 추가하고, 여기에 default.nix 파일을 만들어 두면 nix-shell이나 nix-build 명령어를 실행할 때 Overlay를 참조합니다.

Nixpkgs를 “확장”하거나 “변경”할 때 overlays를 사용한다.

Nix Channels

닉스 패키지는 여러 Nix 채널을 통해 배포된다.

현재 채널 목록 nix-channel --list
primary 채널 추가 nix-channel --add https://nixos.org/channels/channel-name
또 다른 채널 추가 nix-channel --add https://some.channel/url my-alias
채널 제거 nix-channel --remove channel-alias
채널 업데이트 nix-channel --update channel-alias
모든 채널 업데이트 nix-channel --update

Nix store

닉스가 빌드한 패키지들이 위치하는 곳. 읽기 전용이다. 보통 /nix/store. /nix/store/nawl092prjblbhvv16kxxbk6j9gkgcqm-git-2.14.1 앱이름 앞에 해시는, 빌드할 때 써 먹은 모든 입력값(소스, 의존성 트리,컴파일 플래그 등)을 기반으로 만든 값이다. 같은 버전을 여러 빌드에서 써먹으면, 한 번 설치된 것에 심볼릭 링크를 걸어서 쓰는 것 같다.

Profiles

닉스 스토어 항목들을 프로필에 심볼릭 링크를 건다? 이를 이용해서 롤백한다? /nix/var/nix/profiles

닉스OS는 리눅스?

닉스OS는 리눅스이긴 한데, 다른 리눅스에스 돌아가는 바이너리가 그대로 돌지 않는다. 다른 곳에서 컴파일된 바이너리가 의존하는 라이브러들이나 실행 파일, 환경등이 현재 닉스OS에는 없는 상태다. 특정 환경을 끌어다 쓰게 각 종 라이브러리 버전들을 심볼릭 링크를 걸고 있을텐데, 어떤 버전을 쓰는지 알 수 없으니 돌아가지 않는 것 같다.

처음 아래 오류를 만났을 때 의아했다. 쉘 스크립트가 아닌데, 왜 bash: 오류가 나는 걸까 했다.

[nix-shell:~/.local/bin]$ ./powermate 
bash: ./powermate: cannot execute: required file not found

친절한 메시지가 나왔다면 좋았을텐데, 의미를 제대로 전달하는 메시지는 아니다. https://blog.thalheim.io/2022/12/31/nix-ld-a-clean-solution-for-issues-with-pre-compiled-executables-on-nixos/

리눅스가 바이너리를 실행하려면 쉘은 시스템에게 바이너리를 실행하라고 execve 시스템 콜을 한다. strace -f ./powermate로 무슨 일이 일어나고 있는지 들여다 볼 수 있다. file ./powermate로 실행 파일 정보를 자세히 볼 수 있다.

대충 위 사이트를 요약하면, ELF실행 파일은 시스템과 소통하기 위한 라이브러리를 찾을 때 interpreter란 링크 로더를 쓰는데, 다른 리눅스에선 /lib64/ld-linux-x86-64.so.2(x86기반 64비트일 경우)에서 제공하는 걸 쓰는데, 닉스OS의 링크 로더link-loader는 특정 버전을 붙인 걸 쓴다. patchelf로 어떤 버전을 쓰고 있는지 찾을 수 있다.

[lionhairdino@nixos:~/.local/bin]$ patchelf --print-interpreter ./powermate 
/lib64/ld-linux-x86-64.so.2

[lionhairdino@nixos:~/.local/bin]$ patchelf --print-interpreter /run/current-system/sw/bin/ls
/nix/store/p9ysh5rk109gyjj3cn6jr54znvvlahfl-glibc-2.38-66/lib/ld-linux-x86-64.so.2

닉스OS에서 돌아가는 바이너리들은 특정 버전(여기서도 스냅샷이라 부르는지는 아직 잘 모르겠지만, 스택의 스냅샷 같은 것)의 링크 로더(여기선 interpreter)을 쓴다. 위 사이트에선 autoPatchelfHook에 관한 얘기를 하는데, 이는 명령어가 아니라, 닉스OS에서 쓰는 함수인 듯 하다. 이 걸 실행해서 패치하는 건 줄 알았는데, 그렇게 동작하는 명령어는 없다. 일단 넘어가자.

nix-ld로 미리 버전 지정이 없는 것들에 대한 대비를 해놓는 방법이 있고, 바이너리내에 /usr/bin 같이 경로가 하드 코딩되어 있는 것들을 해결하려면 envfs를 쓸 수 있다고 한다.

nix-ld

설정 파일에 아래를 넣으면 좀 더 친절한 에러 메시지가 나온다. programs.nix-ld.enable = true;

[lionhairdino@nixos:~/.local/bin]$ ./powermate 
cannot execute ./powermate: You are trying to run an unpatched binary on nixos, but you have not configured NIX_LD or NIX_LD_x86_64-linux. See https://github.com/Mic92/nix-ld for more details

실행 파일이 어떤 라이브러리를 필요로 하는지 보는 방법 https://unix.stackexchange.com/questions/522822/different-methods-to-run-a-non-nixos-executable-on-nixos

[lionhairdino@nixos:~/.local/bin]$ ldd ./powermate 
	linux-vdso.so.1 (0x00007ffc41f6b000)
	libpulse.so.0 => not found
	libc.so.6 => /nix/store/p9ysh5rk109gyjj3cn6jr54znvvlahfl-glibc-2.38-66/lib/libc.so.6 (0x00007f2ed22f3000)
	/lib64/ld-linux-x86-64.so.2 => /nix/store/p9ysh5rk109gyjj3cn6jr54znvvlahfl-glibc-2.38-66/lib64/ld-linux-x86-64.so.2 (0x00007f2ed24e9000)

보통의 리눅스들이 쓰는 폴더에 /lib64/ld-linux-x86-64.so.2(이걸 shim레이어라고 부르고 있는 것 같은데…)를 설치하고, 실제 링크 로더는 환경 변수 NIX_LD를 써서 지정한다?

[lionhairdino@nixos:~/.local/bin]$ LD_LIBRARY_PATH=/nix/store/g84s3q2rqd93jawhk4fdrsm0z43qxhz6-libpulseaudio-16.1/lib/libpulse.so.0:$LD_LIBRARY_PATH ./powermate 
cannot execute ./powermate: You are trying to run an unpatched binary on nixos, but you have not configured NIX_LD or NIX_LD_x86_64-linux. See https://github.com/Mic92/nix-ld for more details

nix-index

커뮤니티에서 만든 nixpkgs를 위한 파일 데이터베이스 특정 파일을 제공하는 패키지를 알려주는 툴.

[lionhairdino@nixos:~/.local/bin]$ nix-locate libpulse.so.0 --top-level
pulseaudioFull.out                                    0 s /nix/store/79y7fb33sm0xh2bmlbmklwxlbrnfm4fk-pulseaudio-16.1/lib/libpulse.so.0
pulseaudioFull.out                              408,920 x /nix/store/79y7fb33sm0xh2bmlbmklwxlbrnfm4fk-pulseaudio-16.1/lib/libpulse.so.0.24.2
pulseaudio.out                                        0 s /nix/store/zd4r977fl0rvqk8v60dxxarrc4i6k274-pulseaudio-16.1/lib/libpulse.so.0
pulseaudio.out                                  408,920 x /nix/store/zd4r977fl0rvqk8v60dxxarrc4i6k274-pulseaudio-16.1/lib/libpulse.so.0.24.2
libpulseaudio.out                                     0 s /nix/store/g84s3q2rqd93jawhk4fdrsm0z43qxhz6-libpulseaudio-16.1/lib/libpulse.so.0
libpulseaudio.out                               408,920 x /nix/store/g84s3q2rqd93jawhk4fdrsm0z43qxhz6-libpulseaudio-16.1/lib/libpulse.so.0.24.2
libpressureaudio.out                            151,792 x /nix/store/xv5xcs49ap0rpjz9xg6biiaw24pzbbcj-libpressureaudio-0.1.13/lib/libpulse.so.0
libcardiacarrest.out                             88,752 x /nix/store/2bg3lac9jh8akhlb055h1yvvh6f80ksj-libcardiacarrest-12.2.8/lib/libpulse.so.0
apulse.out                                      151,792 x /nix/store/h8hqmyfjxlpsf5lqpa29ihlfkk6pfivq-apulse-0.1.13/lib/apulse/libpulse.so.0

nix-locate

특정 파일을 가지고 있는 패키지 찾기

닉스OS 설정 파일

{ config, pkgs, ... }: # 인자 두 개를 받는 함수다. 
# 아래는 옵션=값 형태의 집합
{ services.httpd.enable = true; 
  services.httpd.adminAddr = "alice@example.org";
  services.httpd.virtualHosts.localhost.documentRoot = "/webroot";
}

그럼 누군가는 위 함수를 부른다는 거겠지? 위 함수에 config, pkgs 매개 변수에 인자를 넘기면, 인자 값에 따라 {옵션=값}을 돌려준다고 보면 되겠다. pkgs로 패키지셋이 넘어오는 것 같다. nixpkgs가 넘어 오겠지?

저널 로그 보는 방법

journalctl -p 0..3 -x

systemdjournal이라는 바이너리로 로그를 저장한다. -n 최근 메시지 10개만 -n 5 최근 메시지 5개만 -x 상세 설명 -e 마지막 메시지부터 -f tail -f와 동일 -p (emerg 0, alert, crit, err, warning, notice, info, debug 7) 우선 순위로 정렬? --since 20240515 --until 20240516 --since "-2hour" --until "10min"

갑자기 순간의 프리징이 보이거나, 아니면 곧바로 gdm 로그인 상태로 가버린다.

다시 로그인하면 작업은 모두 사라져 있다. 저널 로그 확인하니, nouveau 드라이버가 의심간다. https://nixos.wiki/wiki/Nvidia 2024-05-17 nvidia 오피셜 드라이브로 바꾸고 문제는 사라진 것 같다.

flatpak

Nix home-manager

시스템 상태를 선언적으로, 즉 configuration.nix 파일에 모두 모아둘 수 있다면, 현실적으로 configuration.nix 파일만 들고 다니면, 똑같은 시스템을 “재현”할 수 있다. 하지만, DB에 들어 있는 설정에 필요한 정보들은 닉스 패키지 매니저로 관리할 수 없고, 사용자 디렉토리에 있는 dotfiles들도 그렇다. 완벽하게, 한 큐에 재현가능한 메커니즘을 만들기는 어렵다.

사용자 디렉토리는, 시스템과 별개인 사용자 데이터들이 모인 중요한 디렉토리다. 이 디렉토리를 시스템과 묶어서 특정 스냅샷으로 되돌리거나 하면 난리 날 것이다.

이런 위험을 신경쓰지 않고, 사용자 디렉토리에서 “시스템 설정에 필요한 정보”만 따로 잘 컨트롤하기 위해 home-manager를 도입했다.

튜토리얼에선, desktop environment 뿐만 아니라 development environment, compilation environment, cloud virtual machine, 컨테이너 이미지 구성 들도 관리한다고 표현하고 있다. 이런 목적으로 NixOps, colmena 란 툴들 있는 것 같다.

기존 /etc/nix/configuration.nix는 flake.nix의 모듈 정의를 따르고 있어, 그대로 모듈로 불러오면 된다.

Nixpkgs 모듈은 인자 5개를 받는다. lib : nixpkgs에 있는 빌트인 함수들을 모아 놓은 라이브러리 config : 현재 환경에 있는 옵션 값 집합 options : 현재 환경에 있는 모든 모듈에 정의되어 있는 모든 옵션 집합 pkgs : 모든 nixpkgs 패키지를 포함하는 컬렉션과 유틸리티 함수 초보 단계에선 디폴트값nixpkgs.legacyPackages."${system}"만 생각해도 된다. modulesPath : NixOS에서만 유효한 매개 변수, nixpkgs/nixos/modules를 가리킨다. NixOS가 생성한 hardware-configuration.nix 파일에 볼 수 있으며, 추가적인 NixOS 모듈을 import하기 위해 쓰인다.

디폴트가 아닌 매개 변수를 서브 모듈에 넘기는passing 법 _module.args specialArgs

닉스는 시스템 레벨의 설정을 다루지, 사용자 레벨의 설정은 다루지 않는다. 사용자 레벨의 설정을 다루려면 Home Manager 를 설치해야 한다. NixOS의 모듈로 Home Manager를 설치한다.

NixOS 모듈로 설치할 것인가, Home Manager로 설치할 것인가?

root로 작업할 일이 있다. - NixOS 모듈 여러 사용자가 쓸 일이 있다. - NixOS 모듈 NixOS, macOS, 다른 리눅스 배포판에서 모두 돌아가야 할 설정 - Home Manager

sudo nixos-rebuild switch --show-trace --print-build-logs --verbose

위 옵션으로 좀 더 자세한 빌드 오류를 확인할 수 있다.

2024.5 현재 home-manager는 nixos-unstable에서 돌아간다고 한다. https://github.com/nix-community/home-manager/blob/44677a1c96810a8e8c4ffaeaad10c842402647c1/README.md#releases https://discourse.nixos.org/t/home-manager-install-as-module-in-flake-users-option-and-home-manager-cli-not-available/45567

Nix 모듈 시스템에서 의외의 동작이 눈에 띈다. > For example, if program.packages = […] is defined in multiple modules, then imports will merge all program.packages defined in all Nix modules into one list. Attribute sets can also be merged correctly. The specific behavior can be explored by yourself.

여러 모듈에서 program.packages를 정의하고, 모듈들을 import하면 흩어져 있는 program.packages값을 모두 모아 하나의 리스트로 만든다. https://nixos-and-flakes.thiscute.world/nixos-with-flakes/modularize-the-configuration program.packages만 이런 동작을 하는 게 아니라, 설정 파일을 여러 개로 쪼개어 관리할 수 있도록, 동일 항목이 있을 경우 모두 merge하는 동작을 한다. 이 동작이 없다면, 설정을 어떻게 쪼갤까 생각해 보면, 당연한 필요한 동작이다. 하지만, 언어적인 입장에선 애매해 보이는 동작이다.

기존 배포판들과 비교

https://wiki.nixos.org/wiki/Overview_of_the_NixOS_Linux_distribution 닉스OS는 Linux Standard Base 파일 시스템 구조를 따르지 않는다.

LSB는

|시스템 소프트웨어 | /{,usr}/{bin,lib,share} | |설정 파일 | /etc | |사용자 환경에서 쓸 바이너리 | /bin | |동적 라이브러리 | /lib, /usr/lib |

닉스OS는 /lib, /usr/lib가 없다. 시스템 라이브러리, 바이너리, 커널, 펌웨어, 설정 파일 모두 Nix Store에 저장한다. 한 번 Nix Store에 저장되면 수정할 수 없다immutable. 새로운 버전은 다른 해시값으로 저장될 뿐, 기존 파일은 건드리지 않는다. /bin, /usr/bin에는 shebang라인을 가진 스크립트와 호환을 위해 /bin/sh, /usr/bin/env만 들어 있다. 사용자 환경은 Nix Store에 있는 것들을 심볼릭 링크를 걸어 만든다. 이 환경을 profiles라 부른다. /nix/var/nix/profiles에 저장된다. 사용자들은 모두 자신만의 profile을 가진다.

이 구조를 써서 atomicity와 롤백을 지원할 수 있다.

immutable한 Nix Store에 설정 파일 저장하는 걸 눈여겨 봐야 한다. 설정 파일을 “수정”할 수 없다는 뜻이다. 설정 파일은 실행판을 만들어내는 닉스 설정(클래식 환경에선 /etc/nixos/configuration.nix)에서만 가능하고, 이를 설정하고 빌드(nixos-rebuild switch)하면 바뀐 “수정 파일”을 새로 만들어서 Nix Store에 저장한다. 이전에 설정했던 환경으로 롤백이 가능한 이유다.

이렇게 설정해서 “빌드”한 결과물을 generation이라 부른다. 롤백은 “이전 generation으로 돌아가기”라 말하면 되겠다.

$ nix-env --rollback
$ nixos-rebuild switch --rollback

이 전 generation은 부트 로더에서 고를 수도 있다.

새로 generation을 만들어도 이 전 generation은 지워지지 않는다. 설정을 변경해서 빌드할 때마다 generation은 쌓이는데, 다음 명령으로 오래된 걸 삭제한다든지 하며 관리 한다.

# 30일 지난 것들 삭제
$ nix-collect-garbage --delete-older-than 30d

# 모두 삭제
$ nix-collect-garbage -d

# 목록
$ nix-env --list-generations --profile /nix/var/nix/profiles/system

# 특정 generation으로 스위칭
$ nix-env --profile /nix/var/nix/profiles/system --switch-generation 204

# 특정 generation 삭제
$ nix-env --profile /nix/var/nix/profiles/system --delete-generations 205 206

/etc/nixos/configuration.nixnix.gc 옵션을 통해 자동 삭제automatic garbage collection를 지정할 수 있다.

최신 디스코드가 아니라 실행이 안되지만, nixpkgs에는 아직 최신판이 안 올라왔다

nix-shell을 써서 임시로 설치하든가, nix-env로 사용자 환경에서만 설치하든가, overlays로 nixpkgs에 있는 discord를 덮어 씌우든가,

flakes가 활성화된 상태에서 default.nix만 있는 프로젝트 빌드

nix build --no-flake -f default.nix
nix build -f '<nixpkgs>' <패키지명>
Github 계정이 없는 분은 메일로 보내주세요. lionhairdino at gmail.com