Open23

nix flake

110416110416

inputs = {...}: input の set

input は { url : string }
hoge = {fuga = ??? } のようなデータは unexpected flake input attribute fuga になる

inputs = {
   nixpkgs.url = "github:NixOS/nixpkgs";
}

inputs.nixpkgs = {
    type = "github";
    owner = "NixOS";
    repo = "nixpkgs";
}

に対応している. ある種の alias か?
interpreter が下のような処理をしているのかしら.

for (input <- inputs) {
  input <- parse(input.url) <+> (input.type,input.owner,input.repo).mapN(Input)
  // do something
}
110416110416

self: super : パターンは reduce, foldLeft みたいなメンタルモデルで考える. self は最終結果、super は途中の値. reduce のように final: prev: のように書いているケースもある.

110416110416
  fonts.fonts = with pkgs; [
     recursive
     (nerdfonts.override { fonts = [ "JetBrainsMono" ]; })
   ];

を見るとパッケージの何かの値を override 関数で書き換えられそう.

home.packages = [
  (pkgs.coursier.override {version = "..."; })
]

を試してみたら、

error: anonymous function at /nix/store/...source/pkgs/development/tools/coursier/default.nix:1:1 called with unexpected argument 'version'

       at /nix/store/jp3rbrvh2gb9n9c37rrrhzm8xfn0mkbc-source/lib/customisation.nix:69:16:

           68|     let
           69|       result = f origArgs;
             |                ^
           70|
(use '--show-trace' to show detailed location information)

coursier の設定を見ると以下のようになっている. pkgs.coursier は関数か.

override では lib, stdenv, fetchurl, makeWrapper, jre, writeScript, common-updater-scripts, coreutils, git, gnused, nix, nixfmt しか上書きできない...?

{ lib, stdenv, fetchurl, makeWrapper, jre, writeScript, common-updater-scripts
, coreutils, git, gnused, nix, nixfmt }:

let
  version = "2.1.0-M7";

  zshCompletion = fetchurl {
    url =
      "https://raw.githubusercontent.com/coursier/coursier/v${version}/modules/cli/src/main/resources/completions/zsh";
    sha256 = "0afxzrk9w1qinfsz55jjrxydw0fcv6p722g1q955dl7f6xbab1jh";
  };

110416110416

nerdfont の default.nix は以下のようになっている. coursier も引数に version を追加したらバージョンを override で切り替えられる...?

{ stdenv
, fetchurl
, lib
, unzip
# To select only certain fonts, put a list of strings to `fonts`: every key in
# ./shas.nix is an optional font
, fonts ? []
# Whether to enable Windows font variants, their internal font name is limited
# to 31 characters
, enableWindowsFonts ? false
}:

let
  # both of these files are generated via ./update.sh
  version = import ./version.nix;
110416110416

nix derivation のデバッグ

nix repl:l <nixpkgs> をするといろいろなパッケージが import? される.
関数は __functionArgs プロパティをチェックすると必要な引数がわかる.

110416110416

nix derivation を使って scala native のコードをコンパイルする例

src/main.scala
@main def run = println("Hello!")
drv.nix
{ pkgs ? import <nixpkgs>{} }:
pkgs.stdenv.mkDerivation {
  pname = "mypkg";
  version = "0.1.0";

  buildInputs = [
    pkgs.scala-cli
    pkgs.jdk17
    pkgs.which
    pkgs.openssl
  ];
  src = ./src;
  buildPhase = ''
    export HOME="$(mktemp -d)"
    echo $HOME
    scala-cli --power -Duser.home=$HOME package --server=false main.scala --native -o main -f
  '';
  installPhase = ''
    mkdir -p $out/bin
    install main $out/bin/main
  '';
}
nix-build drv.nix

でビルド

110416110416

いくつか罠があって、

  1. -Duser.home=$HOME のように権限のある場所に一時ディレクトリを作って-Duser.home=$HOME で指定しないと /var/empty にアクセスして Operation not permitted になって失敗する. 奇妙なことに JVM かプログラムが $HOME を無視していて -Duser.home で明示的に指定してやらないといけない.
  2. package --server=false-server=false をつけないと bloop が /var/empty にアクセスしてこれまた Operation not permitted になる.
110416110416

また、 bloop が動いているときは path が長すぎると unix domain too long のエラーが出て死ぬ.

110416110416

nix の LSP 実装サーバー nil をインスコする

flake.nix の input に以下を追加

inputs = {
    ....
    nil.url = "github:oxalica/nil";
    ....

output に nil をバインドする

outputs =
    { self, darwin, nixpkgs, nixpkgs-unstable, home-manager, nil, ... }@inputs:

overlays で nil を pkgs に含める

    overlays = {
       nil = final: prev: {
          nil = nil.packages.${prev.stdenv.system}.nil;
        };

nix build する

vs code の場合、nix-ide エクステンションをインスコし以下の設定を追加する

    "nix.enableLanguageServer": true,
    "nix.serverPath": "nil",
    "nix.serverSettings": {
        "nil": {
            "diagnostics": {
                "ignored": [
                    "unused_binding",
                    "unused_with"
                ]
            },
            "formatting": {
                "command": [
                    "nixfmt"
                ]
            }
        }
    },
110416110416

Nix Flake でいい感じに Python の環境構築

{
  description = "Flake to manage my Python workspace.";
  
  inputs.nixpkgs.url = "nixpkgs/nixpkgs-unstable"; 
  inputs.flake-utils.url = "github:numtide/flake-utils";
  outputs = { self, nixpkgs, flake-utils }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = import nixpkgs { inherit system; };
        # pkgs.python の withPackages が pkgs => [pythonPkgs] を受け取るのでここで定義する.
        my-python-packages = ps:
          with ps; [
            pandas
            numpy
            jupyterlab
            # nix 管理下にない PyPi パッケージを追加する.
            (buildPythonPackage rec {
              pname = "<パッケージ名>";
              version = "0.7.5";
              src = fetchPypi {
                inherit pname version;
                sha256 = "sha256-<適当な値を入れてクエリすると失敗と同時に実際の値が得られるのでそれを使う>";
              };
              doCheck = false;
              # パッケージが依存するパッケージをここで指定する.
              propagatedBuildInputs = [
                pkgs.python310Packages.fsspe
              ];
            })
          ];
      in {
        devShell = pkgs.mkShell {
          name = "python-shell";
          buildInputs = with pkgs; [
            (python310.withPackages my-python-packages)
            ruff
            black
          ];
          shellHook = ''
            export PS1='\n\[\033[1;34m\][:\w]\$\[\033[0m\] '
          '';
        };
      });
}

110416110416

Nix flake でいい感じに Rust の環境構築

{
  description = "Flake to manage Rust workspace.";

  inputs.nixpkgs.url = "nixpkgs/nixpkgs-unstable";
  inputs.flake-utils.url = "github:numtide/flake-utils";
  inputs.rust-overlay.url = "github:oxalica/rust-overlay";
  outputs = { self, nixpkgs, rust-overlay, flake-utils }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        overlays = [ (import rust-overlay) ];
        pkgs = import nixpkgs { inherit system overlays; };
      in {
        devShell = pkgs.mkShell {
          name = "rust-shell";
          buildInputs = with pkgs; [
            rust-bin.beta.latest.default
            cargo-udeps
            iconv
            rust-analyzer
          ];
        };
      });
}
110416110416

以下の一連の作業で disk image が作れる.

git clone  git@github.com:NixOS/nixpkgs.git && cd nixpkgs
git checkout nixos-22.11

https://nixos.wiki/wiki/Install_NixOS_on_GCE の指示に従ってイメージをビルドする. M1 Mac でやるので Docker を利用する.

cd ..
docker run --rm -it --platform=linux/amd64 -v `pwd`/nixpkgs:/nixpkgs --workdir /nixpkgs nixpkgs/nix:nixos-22.11 bash

gcloud sdk を利用するため一時的に unstable チャンネルを利用する. 作業が終わったら unset する.

NIX_PATH=nixpkgs=channel:nixos-unstable
nix-shell -p 'google-cloud-sdk'
export NIX_CONFIG=$'system-features = benchmark big-parallel nixos-test uid-range kvm\\nfilter-syscalls = false\\nexperimental-features = nix-command flakes'

上のオプションはトラブルシューティングしながら見つけた以下のサイトを参考にした.

Google Storage bucket BUCKET_NAME を作る.

BUCKET_NAME=nix-gce-img nixpkgs/nixos/maintainers/scripts/gce/create-gce.sh

NOTE:
GCS への up が失敗したら成果物に含まれる tar.gz を手動で gcs にアップロードしhttps://cloud.google.com/compute/docs/import/import-existing-image の指示に従ってイメージを作ればいい.

ここまで来ればイメージカラ GCE インスタンスを作成できる.

ssh で接続して動作確認する.

gcloud compute ssh --zone "<ZONE>" "<INSTANCE_NAME>" --project "<PROJECT>"      
sudo -i
110416110416

network 周りのメモ

ゴールは nix-shell を使えるように public ip を公開しないでインターネットに接続する.
NAT を設定すればパブリックな ip アドレスがなくてもビルド時にインターネットアクセスできる.

ping はポートを指定できないので疎通確認は nc で.

nc -v -w 1 <private ip> -z <port>

GCP の network の firewall rules のデフォルト

VPC が作成されると、いくつかのルートがシステムよって自動生成される. その中には 0.0.0.0/0 をデフォルトインターネットゲートウェイへ向けるルートも存在している.
ファイアウォールルールの項で解説したように、デフォルトでは下り (Egress) ルールは 0.0.0.0/0 に対し許可、上り (Ingress) では 0.0.0.0/0 からを拒否している

https://blog.g-gen.co.jp/entry/vpc-explained-basics

Identity aware proxy からのトラフィックは "35.235.240.0/20" からくるのでこれを許可しないと GCP のIAM認証を使ってインスタンスに接続できない.

resource "google_compute_firewall" "allow_ssh_traffic_via_iap" {
  name      = "allow-ssh-traffic-via-iap"
  network   = google_compute_network.hoge.name
  priority  = "65534"
  direction = "INGRESS"
  allow {
    protocol = "tcp"
    ports    = ["22"]
  }
  source_ranges = [
    "35.235.240.0/20"
  ]
}
110416110416

これで nix-shell -p vim などができるようになった.
次に以下のサイトを参考に flake を有効化する.

https://nixos-and-flakes.thiscute.world/nixos-with-flakes/nixos-with-flakes-enabled

以下はインスタンスに flake を導入するためのミニマルな configuration.nix

{ config, pkgs, ... }:

{
  imports =
    [ # Include the results of the hardware scan.
      ./hardware-configuration.nix
    ];

  # Omit the previous configuration...

  # Enable Flakes and the new command-line tool
  nix.settings.experimental-features = [ "nix-command" "flakes" ];

  environment.systemPackages = with pkgs; [
    # Flakes use Git to pull dependencies from data sources 
    git
    vim
    wget
    curl
  ];
  # Set default editor to vim
  environment.variables.EDITOR = "vim";

  # Omit the rest of the configuration...
}
110416110416

どこかの段階で configuration.nix や flake.nix をローカルに持ってきて git 管理・デプロイ自動化する方がいい.

110416110416

memo:

export VERSION=0.1.0
gcloud compute scp foo-$VERSION.jar instance:/foo-$VERSION.jar
gcloud compute ssh --  (ここでfoo-$VERSION.jar を実行するプログラムに指定する処理を書く) && nixos-rebuild switch

とかすればできそう?