feat: add a separate chapater for nix store & binary cache (#144)

This commit is contained in:
Ryan Yin 2024-04-08 01:14:54 +08:00 committed by GitHub
parent 3d77080e71
commit a87d5dc0b0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 821 additions and 540 deletions

View File

@ -233,6 +233,20 @@ function themeConfigEnglish() {
},
],
},
{
text: "Nix Store & Binary Cache",
items: [
{ text: "Introduction", link: "/nix-store/intro.md" },
{
text: "Add Binary Cache Servers",
link: "nix-store/add-binary-cache-servers.md",
},
{
text: "Host Your Own Binary Cache Server",
link: "/nix-store/host-your-own-binary-cache-server.md",
},
],
},
{
text: "Best Practices",
items: [
@ -261,10 +275,6 @@ function themeConfigEnglish() {
text: "Debugging Derivations and Nix Expressions",
link: "/best-practices/debugging.md",
},
{
text: "Host Custom Binary Cache with S3",
link: "/best-practices/host-custom-binary-cache-with-s3.md",
},
],
},
@ -439,6 +449,20 @@ function themeConfigChinese() {
},
],
},
{
text: "Nix Store 与二进制缓存",
items: [
{ text: "简介", link: "/zh/nix-store/intro.md" },
{
text: "添加二进制缓存服务器",
link: "/zh/nix-store/add-binary-cache-servers.md",
},
{
text: "搭建你自己的缓存服务器",
link: "/zh/nix-store/host-your-own-binary-cache-server.md",
},
],
},
{
text: "NixOS 最佳实践",
items: [
@ -467,10 +491,6 @@ function themeConfigChinese() {
text: "调试 Nix 软件包与 Nix 表达式",
link: "/zh/best-practices/debugging.md",
},
{
text: "使用 S3 托管自定义二进制缓存",
link: "/zh/best-practices/host-custom-binary-cache-with-s3.md",
},
],
},
{

View File

@ -1,267 +0,0 @@
# Host Custom Binary Cache with S3 {#host-custom-binary-cache-with-s3}
## TL;DR
A guide on how to set up your own S3 nix binary cache using MinIO S3 server.
## How Software Stored in Nix? {#how-software-stored-in-nix}
Multiple versions of the same software package can be installed on a system, making it
possible to satisfy various dependency chains at a time. This enables the installation of
multiple packages that depend on the same third package, but on different versions of it.
To achieve this, all packages are installed in the global nix store under `/nix/store/`
and are then symlinked to the respective locations. To verify a package's uniqueness, its
whole directory is hashed, and the hash put into the name of the package's main folder.
Every software built from the same Nix expression that uses the same dependency software
versions results in the same hash, no matter what system it was built on. If any of the
dependency software versions were changed, this will result in a new hash for the final
package.
Using symlinks to install a package and link all the right dependencies to it also enables
atomic updating. To make this clearer, let's think of an example where software X is
installed in an older version and should be updated. Software X is installed in its very
own directory in the global nix store and symlinked to the right directory, let's say
`/usr/local/bin/`. When the update is triggered, the new version of X is installed into
the global nix store without interfering with its older version. Once the installation
with all its dependencies in the nix store is completed, the final step is to change the
symlink to `/usr/local/bin/`. Since creating a new symlink that overwrites the old one is
an atomic operation in Unix, it is impossible for this operation to fail and leave the
package in a corrupted state. The only possible problem would be that it fails before or
after the symlink creation. Either way, the result would be that we either have the old
version of X or the newly installed version, but nothing in between.
Quoted from the original work of
https://medium.com/earlybyte/the-s3-nix-cache-manual-e320da6b1a9b
## Nix Binary Caches {#nix-binary-caches}
No matter how great every aspect of Nix sounds, its design has also a major drawback,
which is that every package build triggers the build process for the whole dependency
chain from scratch. This can take quite a while, since even compilers such as gcc or ghc
must be built in advance. Such build processes can eat up a remarkable amount of memory,
introducing an additional hurdle if one wants to use it on restricted platforms such as
the Raspberry Pi.
To overcome the drawback of always building everything from scratch and the chance of
losing access to prebuilt versions of packages, it is also possible to build your Nix
binary cache using an S3 server such as MinIO (https://min.io/).
The process of setting up your cache, populating it with binaries and use the binaries
from your newly built cache will be described step by step. For this manual, I assume that
you already have nix in place, and that you already have a running MinIO server somewhere
in your environment. If not, you may check out the
[official deployment guide](https://min.io/docs/minio/linux/operations/installation.html)
from MinIO. You'll need to ensure that MinIO is accessible via `HTTPS` using a trusted
certificate. Let's Encrypt will be helpful here.
In this post, let's explore how we can self-host an S3-compatible server, MinIO, as a
binary cache store.
Quoted from the original work of
https://medium.com/earlybyte/the-s3-nix-cache-manual-e320da6b1a9b
## How To Use S3 as a Binary Cache Server {#how-to-use-s3-as-a-binary-cache-server}
### Prerequisites {#prerequisites}
- Set up MinIO somewhere in your environment.
- Hold a valid SSL certificates either public or private. For demonstration purpose, we
will use `minio.homelab.local` (private certificate) in the steps mentioned in this
tutorial. If you plan to use a private certificate, you MUST resolve the DNS challenges
on your own. Hence, it is recommended to use a public certificate.
- Install `minio-client` in your environment.
### Generate Password {#generate-password}
```bash
nix run nixpkgs#pwgen -- -c -n -y -s -B 32 1
# oenu1Yuch3rohz2ahveid0koo4giecho
```
### Set Up MinIO Client {#set-up-minio-client}
Install the MinIO command-line client `mc`.
```nix
{ pkgs, ... }:
{
environment.systemPackages = with pkgs; [
minio-client # A replacement for ls, cp, mkdir, diff, and rsync commands for filesystems and object storage
];
}
```
Create or edit `~/.mc/config.json`.
```json
{
"version": "10",
"aliases": {
"s3": {
"url": "https://s3.homelab.local",
"accessKey": "minio",
"secretKey": "oenu1Yuch3rohz2ahveid0koo4giecho",
"api": "s3v4",
"path": "auto"
}
}
}
```
On the machine we use to build the nix packages, we need the S3 credentials in places. Set
up a file `~/.aws/credentials` and populate it with the credentials of our nixbuilder
user. Replace `<nixbuildersecret>` with the password generated by the `pwgen` command.
```bash
[default]
aws_access_key_id=nixbuilder
aws_secret_access_key=<nixbuildersecret>
```
### Setup S3 Bucket as Binary Cache {#setup-s3-bucket-as-binary-cache}
Create the `nix-cache` bucket.
```bash
mc mb s3/nix-cache
```
Create the `nixbuilder` MinIO user and assign a password.
```bash
mc admin user add s3 nixbuilder <PASSWORD>
```
Create a file called `nix-cache-write.json` in your current working directory with the
following contents:
```json
{
"Id": "AuthenticatedWrite",
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AuthenticatedWrite",
"Action": [
"s3:AbortMultipartUpload",
"s3:GetBucketLocation",
"s3:GetObject",
"s3:ListBucket",
"s3:ListBucketMultipartUploads",
"s3:ListMultipartUploadParts",
"s3:PutObject"
],
"Effect": "Allow",
"Resource": ["arn:aws:s3:::nix-cache", "arn:aws:s3:::nix-cache/*"],
"Principal": "nixbuilder"
}
]
}
```
Create a policy with `nix-cache-write.json` that allows `nixbuilder` to upload files to
the cache.
```bash
mc admin policy add s3 nix-cache-write nix-cache-write.json
```
Associate the policy that we created above with the `nixbuilder` user.
```bash
mc admin policy set s3 nix-cache-write user=nixbuilder
```
Allow anonymous users to download files without authenticating.
```bash
mc anonymous set download s3/nix-cache
```
Create a file called `nix-cache-info` in your working directory. This file tells Nix that
the bucket is indeed a binary cache.
```bash
cat > nix-cache-info <<EOF
StoreDir: /nix/store
WantMassQuery: 1
Priority: 40
EOF
```
Copy `nix-cache-info` to the cache bucket.
```bash
mc cp ./nix-cache-info s3/nix-cache/nix-cache-info
```
### Generate Key Pairs {#generate-key-pairs}
Generate a secret and public key for signing store paths. The key name is arbitrary, but
the NixOS developers highly recommend using the domain name of the cache followed by an
integer. If the key ever needs to be revoked or regenerated, the trailing integer can be
incremented.
```bash
nix key generate-secret --key-name s3.homelab.local-1 > ~/.config/nix/secret.key
nix key convert-secret-to-public < ~/.config/nix/secret.key > ~/.config/nix/public.key
cat ~/.config/nix/public.key
# s3.homelab.local-1:m0J/oDlLEuG6ezc6MzmpLCN2MYjssO3NMIlr9JdxkTs=
```
### Activate Binary Cache with Flake {#activate-binary-cache-with-flake}
Put the following lines in `configuration.nix` or any of your custom NixOS module:
```nix
{
nix = {
settings = {
# Substituters will be appended to the default substituters when fetching packages.
extra-substituters = [
"https://s3.homelab.local/nix-cache/"
];
extra-trusted-public-keys = [
"s3.homelab.local-1:m0J/oDlLEuG6ezc6MzmpLCN2MYjssO3NMIlr9JdxkTs="
];
};
};
}
```
Rebuild the system.
```bash
sudo nixos-rebuild switch --upgrade --flake .#<HOST>
```
### Push Paths to the Store {#push-paths-to-the-store}
Sign some paths in the local store.
```bash
nix store sign --recursive --key-file ~/.config/nix/secret.key /run/current-system
```
Copy those paths to the cache.
```bash
nix copy --to 's3://nix-cache?profile=nixbuilder&endpoint=s3.homelab.local' /run/current-system
```
### Add Automatic Object Expiration Policy {#add-automatic-object-expiration-policy}
```bash
mc ilm rule add s3/nix-cache --expire-days "DAYS"
# Example: mc ilm rule add s3/nix-cache --expire-days "7"
```
### References {#references}
Here are some of the sources that I used in making this document:
- [Blog post by Jeff on Nix binary caches](https://jcollie.github.io/nixos/2022/04/27/nixos-binary-cache-2022.html)
- [Binary cache in the NixOS wiki](https://nixos.wiki/wiki/Binary_Cache)
- [Serving a Nox store via S3 in the NixOS manual](https://nixos.org/manual/nix/stable/package-management/s3-substituter.html)
- [Serving a Nix store via HTTP in the NixOS manual](https://nixos.org/manual/nix/stable/package-management/binary-cache-substituter.html)

View File

@ -1,23 +1,21 @@
# Adding Custom Cache Servers {#add-custom-cache-servers}
# Adding Binary Cache Servers
## What is Nix Cache Server {#what-is-nix-cache-server}
We have introduced the concepts of Nix Store and binary cache. Here, we will see how to
add multiple cache servers to speed up package downloads.
## Why Add Cache Servers {#why-add-cache-servers}
Nix provides an official cache server, [https://cache.nixos.org](https://cache.nixos.org),
which caches build results for all packages in nixpkgs under commonly used CPU
architectures. When you execute Nix build commands locally, if Nix finds a corresponding
cache on the server, it directly downloads the cached file, skipping the time-consuming
local build process and significantly improving build speed.
## Why Add Custom Cache Servers {#why-add-custom-cache-servers}
Two reasons:
which caches build results for most commonly used packages. However, it may not meet all
users' needs. In the following cases, we need to add additional cache servers:
1. Add cache servers for some third-party projects, such as the nix-community cache server
[https://nix-community.cachix.org](https://nix-community.cachix.org), which can
significantly improve the build speed of these third-party projects.
1. Add cache server mirror sites closest to the user to speed up downloads.
1. Add a self-built cache server to speed up the build process of personal projects.
## How to Add Custom Cache Servers {#how-to-add-custom-cache-servers}
## How to Add Cache Servers {#how-to-add-custom-cache-servers}
In Nix, you can configure cache servers using the following options:

View File

@ -0,0 +1,228 @@
# Using S3 for Custom Binary Cache Hosting
## Introduction
The Nix binary cache is an implementation of the Nix Store that stores data on a remote
server rather than locally, facilitating the sharing of binary caches across multiple
machines.
The official Nix binary cache server only provides binaries built with standard
parameters. If you've customized build parameters or are using packages outside of
Nixpkgs, Nix won't find the corresponding binary cache, resulting in local builds.
Relying solely on your local Nix Store `/nix/store` can be cumbersome, as you'd need to
rebuild all your custom packages on each machine, which can be time-consuming and
memory-intensive. This situation is exacerbated on lower-performance platforms like
Raspberry Pi.
This document will show you how to set up your own Nix binary cache server using an S3
service (like MinIO) to share build results across machines and address the aforementioned
issues.
## Prerequisites
1. A NixOS host
1. Deployed MinIO server
1. If not, you can follow MinIO's
[official deployment guide](https://min.io/docs/minio/linux/operations/installation.html).
1. The MinIO server needs a valid TLS digital certificate, which can be public or private.
This example will use `https://minio.homelab.local` with a private certificate.
1. Install `minio-client`
## Generating a Password
```bash
nix run nixpkgs#pwgen -- -c -n -y -s -B 32 1
# => oenu1Yuch3rohz2ahveid0koo4giecho
```
## Setting Up the MinIO Client
Install the MinIO command-line client `mc`.
```nix
{ pkgs, ... }:
{
environment.systemPackages = with pkgs; [
minio-client # Alternatives for ls, cp, mkdir, diff, and rsync commands for file systems and object storage
];
}
```
Create `~/.mc/config.json` with the following content (replace the key parameters with
your own):
```json
{
"version": "10",
"aliases": {
"s3": {
"url": "https://s3.homelab.local",
"accessKey": "minio",
"secretKey": "oenu1Yuch3rohz2ahveid0koo4giecho",
"api": "s3v4",
"path": "auto"
}
}
}
```
Since Nix will interact directly with the S3 bucket, we need to configure S3 credentials
for all machines that require access to the Nix binary cache.
Create `~/.aws/credentials` with the following content (replace `<nixbuildersecret>` with
the password generated by the `pwgen` command).
```toml
[default]
aws_access_key_id=nixbuilder
aws_secret_access_key=<nixbuildersecret>
```
## Setting Up S3 Bucket as Binary Cache
Create the `nix-cache` bucket using the minio client:
```bash
mc mb s3/nix-cache
```
Create the `nixbuilder` user for MinIO and assign it a password:
```bash
mc admin user add s3 nixbuilder <PASSWORD>
```
Create a file named `nix-cache-write.json` in the current working directory with the
following content:
```json
{
"Id": "AuthenticatedWrite",
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AuthenticatedWrite",
"Action": [
"s3:AbortMultipartUpload",
"s3:GetBucketLocation",
"s3:GetObject",
"s3:ListBucket",
"s3:ListBucketMultipartUploads",
"s3:ListMultipartUploadParts",
"s3:PutObject"
],
"Effect": "Allow",
"Resource": ["arn:aws:s3:::nix-cache", "arn:aws:s3:::nix-cache/*"],
"Principal": "nixbuilder"
}
]
}
```
Now, create a policy for uploading files to S3 using the `nix-cache-write.json` file:
```bash
mc admin policy add s3 nix-cache-write nix-cache-write.json
```
Associate the S3 policy we just created with the `nixbuilder` user:
```bash
mc admin policy set s3 nix-cache-write user=nixbuilder
```
Allow anonymous users to download files without authentication, so all Nix servers can
pull data directly from this S3 cache:
```bash
mc anonymous set download s3/nix-cache
```
Finally, add the `nix-cache-info` file to the S3 bucket root directory, as Nix requires
this file to record some information related to the binary cache:
```bash
cat > nix-cache-info <<EOF
StoreDir: /nix/store
WantMassQuery: 1
Priority: 40
EOF
# Copy `nix-cache-info` to the S3 bucket
mc cp ./nix-cache-info s3/nix-cache/nix-cache-info
```
## Generating Signature Key Pair
As mentioned earlier, the Nix binary cache uses a public key signature mechanism to verify
the origin and integrity of the data, so we need to generate a key pair for our Nix build
machine to sign the binary cache. The key name is arbitrary, but NixOS developers strongly
recommend using the cache domain followed by an integer, so if the key needs to be revoked
or regenerated, you can simply increment the integer at the end.
```bash
nix key generate-secret --key-name s3.homelab.local-1 > ~/.config/nix/secret.key
nix key convert-secret-to-public < ~/.config/nix/secret.key > ~/.config/nix/public.key
cat ~/.config/nix/public.key
# => s3.homelab.local-1:m0J/oDlLEuG6ezc6MzmpLCN2MYjssO3NMIlr9JdxkTs=
```
## Using S3 Binary Cache in `flake.nix`
Add the following to your `configuration.nix` or any custom NixOS module:
```nix
{
nix = {
settings = {
# The substituter will be appended to the default substituters when fetching packages.
extra-substituters = [
"https://s3.homelab.local/nix-cache/"
];
extra-trusted-public-keys = [
"s3.homelab.local-1:m0J/oDlLEuG6ezc6MzmpLCN2MYjssO3NMIlr9JdxkTs="
];
};
};
}
```
Rebuild the system to start using our newly created S3 binary cache:
```bash
sudo nixos-rebuild switch --upgrade --flake .#<HOST>
```
## Pushing Store Paths to Binary Cache
Sign some paths in the local store.
```bash
nix store sign --recursive --key-file ~/.config/nix/secret.key /run/current-system
```
Copy these paths to the cache:
```bash
nix copy --to 's3://nix-cache?profile=nixbuilder&endpoint=s3.homelab.local' /run/current-system
```
## Adding Automatic Object Expiration Policy
```bash
mc ilm rule add s3/nix-cache --expire-days "DAYS"
# For example: mc ilm rule add s3/nix-cache --expire-days "7"
```
This will set an expiration policy for objects in the S3 bucket, ensuring that they are
automatically removed after a specified number of days.
This is useful for keeping the cache size manageable and ensuring that outdated binaries
are not stored indefinitely.
### References {#references}
- [Blog post by Jeff on Nix binary caches](https://jcollie.github.io/nixos/2022/04/27/nixos-binary-cache-2022.html)
- [Binary cache in the NixOS wiki](https://nixos.wiki/wiki/Binary_Cache)
- [Serving a Nox store via S3 in the NixOS manual](https://nixos.org/manual/nix/stable/package-management/s3-substituter.html)
- [Serving a Nix store via HTTP in the NixOS manual](https://nixos.org/manual/nix/stable/package-management/binary-cache-substituter.html)

178
docs/nix-store/intro.md Normal file
View File

@ -0,0 +1,178 @@
# Nix Store and Binary Cache
Here we provide a brief introduction to the Nix Store, Nix binary cache, and related
concepts, without delving into specific configurations and usage methods, which will be
covered in detail in subsequent chapters.
## Nix Store
The Nix Store is one of the core concepts of the Nix package manager. It is a read-only
file system used to store all files that require immutability, including the build results
of software packages, metadata of software packages, and all build inputs of software
packages.
The Nix package manager uses the Nix functional language to describe software packages and
their dependencies. Each software package is treated as the output of a pure function, and
the build results of the software package are stored in the Nix Store.
Data in the Nix Store has a fixed path format:
```
/nix/store/b6gvzjyb2pg0kjfwrjmg1vfhh54ad73z-firefox-33.1
|--------| |------------------------------| |----------|
store directory digest name
```
As seen, paths in the Nix Store start with a hash value (digest), followed by the name and
version number of the software package. This hash value is calculated based on all input
information of the software package (build parameters, dependencies, dependency versions,
etc.), and any changes in build parameters or dependencies will result in a change in the
hash value, thus ensuring the uniqueness of each software package path. Additionally,
since the Nix Store is a read-only file system, it ensures the immutability of software
packages - once a software package is built, it will not change.
Because the storage path of the build result is calculated based on all input information
of the build process, **the same input information will yield the same storage path**.
This design is also known as the input-addressed model (_Input-addressed Model_).
### How NixOS Uses the Nix Store
NixOS's declarative configuration calculates which software packages need to be installed
and then soft-links the storage paths of these packages in the Nix Store to
`/run/current-system`, and by modifying environment variables like `PATH` to point to the
corresponding folder in `/run/current-system`, the installation of software packages is
achieved. Each time a deployment is made, NixOS calculates the new system configuration,
cleans up old symbolic links, and re-creates new symbolic links to ensure that the system
environment matches the declarative configuration.
home-manager works similarly, soft-linking the software packages configured by the user to
`/etc/profiles/per-user/your-username` and modifying environment variables like `PATH` to
point to this path, thus installing user software packages.
```bash
# Check where bash in the environment comes from (installed using NixOS)
which bash
╭───┬─────────┬─────────────────────────────────┬──────────╮
│ # │ command │ path │ type │
├───┼─────────┼─────────────────────────────────┼──────────┤
│ 0 │ bash │ /run/current-system/sw/bin/bash │ external │
╰───┴─────────┴─────────────────────────────────┴──────────╯
ls -al /run/current-system/sw/bin/bash
lrwxrwxrwx 15 root root 76 1970年 1月 1日 /run/current-system/sw/bin/bash -> /nix/store/1zslabm02hi75anb2w8zjrqwzgs0vrs3-bash-interactive-5.2p26/bin/bash
# Check where cowsay in the environment comes from (installed using home-manager)
which cowsay
╭───┬─────────┬────────────────────────────────────────┬──────────╮
│ # │ command │ path │ type │
├───┼─────────┼────────────────────────────────────────┼──────────┤
│ 0 │ cowsay │ /etc/profiles/per-user/ryan/bin/cowsay │ external │
╰───┴─────────┴────────────────────────────────────────┴──────────╯
ls -al /etc/profiles/per-user/ryan/bin/cowsay
lrwxrwxrwx 2 root root 72 1970年 1月 1日 /etc/profiles/per-user/ryan/bin/cowsay -> /nix/store/w2czyf82gxz4vy9kzsdhr88112bmc0c1-home-manager-path/bin/cowsay
```
The `nix develop` command, on the other hand, directly adds the storage paths of software
packages to environment variables like `PATH` and `LD_LIBRARY_PATH`, enabling the newly
created shell environment to directly use these software packages or libraries.
For example, in the source code repository for this book,
[ryan4yin/nixos-and-flakes-book](https://github.com/ryan4yin/nixos-and-flakes-book), after
executing the `nix develop` command, we can examine the contents of the `PATH` environment
variable:
```bash
nix develop
node v20.9.0
env | egrep '^PATH'
PATH=/nix/store/h13fnmpm8m28qypsba2xysi8a90crphj-pre-commit-3.6.0/bin:/nix/store/2mqyvwp96d4jynsnzgacdk5rg1kx2a9a-node2nix-1.11.0/bin:/nix/store/a1hckfqzyys4rfgbdy5kmb5w0zdr55i5-nodejs-20.9.0/bin:/nix/store/gjrfcl2bhv7kbj883k7b18n2aprgv4rf-pnpm-8.10.2/bin:/nix/store/z6jfxqyj1wq62iv1gn5b5d9ms6qigkg0-yarn-1.22.19/bin:/nix/store/2k5irl2cfw5m37r3ibmpq4f7jndb41a8-prettier-3.0.3/bin:/nix/store/zrs710jpfn7ngy5z4c6rrwwjq33b2a0y-git-2.42.0/bin:/nix/store/dkmyyrkyl0racnhsaiyf7rxf43yxhx92-typos-1.16.23/bin:/nix/store/imli2in1nr1h8qh7zh62knygpl2zj66l-alejandra-3.0.0/bin:/nix/store/85jldj870vzcl72yz03labc93bwvqayx-patchelf-0.15.0/bin:/nix/store/90h6k8ylkgn81k10190v5c9ldyjpzgl9-gcc-wrapper-12.3.0/bin:/nix/store/hf2gy3km07d5m0p1lwmja0rg9wlnmyr7-gcc-12.3.0/bin:/nix/store/cx01qk0qyylvkgisbwc7d3pk8sliccgh-glibc-2.38-27-bin/bin:/nix/store/bblyj5b3ii8n6v4ra0nb37cmi3lf8rz9-coreutils-9.3/bin:/nix/store/1alqjnr40dsk7cl15l5sn5y2zdxidc1v-binutils-wrapper-2.40/bin:/nix/store/1fn92b0783crypjcxvdv6ycmvi27by0j-binutils-2.40/bin:/nix/store/bblyj5b3ii8n6v4ra0nb37cmi3lf8rz9-coreutils-9.3/bin:/nix/store/l974pi8a5yqjrjlzmg6apk0jwjv81yqw-findutils-4.9.0/bin:/nix/store/8q25nyfirzsng6p57yp8hsaldqqbc7dg-diffutils-3.10/bin:/nix/store/9c5qm297qnvwcf7j0gm01qrslbiqz8rs-gnused-4.9/bin:/nix/store/rx2wig5yhpbwhnqxdy4z7qivj9ln7fab-gnugrep-3.11/bin:/nix/store/7wfya2k95zib8jl0jk5hnbn856sqcgfk-gawk-5.2.2/bin:/nix/store/xpidksbd07in3nd4sjx79ybwwy81b338-gnutar-1.35/bin:/nix/store/202iqv4bd7lh6f7fpy48p7q4d96lqdp7-gzip-1.13/bin:/nix/store/ik7jardq92dxw3fnz3vmlcgi9c8dwwdq-bzip2-1.0.8-bin/bin:/nix/store/v4iswb5kwj33l46dyh2zqh0nkxxlr3mz-gnumake-4.4.1/bin:/nix/store/q1c2flcykgr4wwg5a6h450hxbk4ch589-bash-5.2-p15/bin:/nix/store/cbj1ph7zi009m53hxs90idl1f5i9i941-patch-2.7.6/bin:/nix/store/76z4cjs7jj45ixk12yy6k5z2q2djk2jb-xz-5.4.4-bin/bin:/nix/store/qmfxld7qhk8qxlkx1cm4bkplg1gh6jgj-file-5.45/bin:/home/ryan/.local/bin:/home/ryan/go/bin:/home/ryan/.config/emacs/bin:/home/ryan/.local/bin:/home/ryan/go/bin:/home/ryan/.config/emacs/bin:/nix/store/jsc6jydv5zjpb3dvh0lxw2dzxmv3im9l-kitty-0.32.1/bin:/nix/store/ihpdcszhj8bdmyr0ygvalqw9zagn0jjz-imagemagick-7.1.1-28/bin:/nix/store/2bm2yd5jqlwf6nghlyp7z88g28j9n8r0-ncurses-6.4-dev/bin:/run/wrappers/bin:/guix/current/bin:/home/ryan/.guix-home/profile/bin:/home/ryan/.guix-profile/bin:/home/ryan/.nix-profile/bin:/nix/profile/bin:/home/ryan/.local/state/nix/profile/bin:/etc/profiles/per-user/ryan/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin:/nix/store/c53f8hagyblvx52zylsnqcc0b3nxbrcl-binutils-wrapper-2.40/bin:/nix/store/fpagbmzdplgky01grwhxcsazvhynv1nz-pciutils-3.10.0/bin:/nix/store/4cjqvbp1jbkps185wl8qnbjpf8bdy8j9-gcc-wrapper-13.2.0/bin
```
Clearly, `nix develop` has added the storage paths of many software packages directly to
the `PATH` environment variable.
## Nix Store Garbage Collection
The Nix Store is a centralized storage system where all software package build inputs and
outputs are stored. As the system is used, the number of software packages in the Nix
Store will increase, and the disk space occupied will grow larger.
To prevent the Nix Store from growing indefinitely, the Nix package manager provides a
garbage collection mechanism for the local Nix Store, to clean up old data and reclaim
storage space.
According to
[Chapter 11. The Garbage Collector - nix pills](https://nixos.org/guides/nix-pills/garbage-collector),
the `nix-store --gc` command performs garbage collection by recursively traversing all
symbolic links in the `/nix/var/nix/gcroots/` directory to find all referenced packages
and delete those that are no longer referenced. The `nix-collect-garbage --delete-old`
command goes a step further by first deleting all old
[profiles](https://nixos.org/manual/nix/stable/command-ref/files/profiles) and then
running the `nix-store --gc` command to clean up packages that are no longer referenced.
It's important to note that build results from commands like `nix build` and `nix develop`
are not automatically added to `/nix/var/nix/gcroots/`, so these build results may be
cleaned up by the garbage collection mechanism. You can use `nix-instantiate` with
`keep-outputs = true` and other means to avoid this, but I currently prefer setting up
your own binary cache server and configuring a longer cache time (e.g., one year), then
pushing data to the cache server. This way, you can share build results across machines
and avoid having local build results cleaned up by the local garbage collection mechanism,
achieving two goals in one.
## Binary Cache
The design of Nix and the Nix Store ensures the immutability of software packages,
allowing build results to be shared directly between multiple machines. As long as these
machines use the same input information to build a package, they will get the same output
path, and Nix can reuse the build results from other machines instead of rebuilding the
package, thus speeding up the installation of software packages.
The Nix binary cache is designed based on this feature; it is an implementation of the Nix
Store that stores data on a remote server instead of locally. When needed, the Nix package
manager downloads the corresponding build results from the remote server to the local
`/nix/store`, avoiding the time-consuming local build process.
Nix provides an official binary cache server at <https://cache.nixos.org>, which caches
build results for most packages in nixpkgs for common CPU architectures. When you execute
a Nix build command on your local machine, Nix first attempts to find the corresponding
binary cache on the cache server. If found, it will directly download the cache file,
bypassing the time-consuming local compilation and greatly accelerating the build process.
## Nix Binary Cache Trust Model
The **Input-addressed Model** only guarantees that the same input will produce the same
output path, but it does not ensure the uniqueness of the output content. This means that
even with the same input information, multiple builds of the same software package may
produce different output content.
While Nix has taken measures such as disabling network access in the build environment and
using fixed timestamps to minimize uncertainty, there are still some uncontrollable
factors that can influence the build process and produce different output content. These
differences in output content typically do not affect the functionality of the software
package but do pose a challenge for the secure sharing of binary cache - the uncertainty
in output content makes it difficult to determine whether the binary cache downloaded from
the cache server was indeed built with the declared input information, and whether it
contains malicious content.
To address this, the Nix package manager uses a public-private key signing mechanism to
verify the source and integrity of the binary cache. This places the responsibility of
security on the user. If you wish to use a non-official cache server to speed up the build
process, you must add the public key of that server to `trusted-public-keys` and assume
the associated security risks - the cache server might provide cached data that includes
malicious content.
### Content-addressed Model
[RFC062 - content-addressed store paths](https://github.com/NixOS/rfcs/blob/master/rfcs/0062-content-addressed-paths.md)
is an attempt by the community to improve build result consistency. It proposes a new way
to calculate storage paths based on the build results (outputs) rather than the input
information (inputs). This design ensures consistency in build results - if the build
results are different, the storage paths will also be different, thus avoiding the
uncertainty in output content inherent in the input-addressed model.
However, this approach is still in an experimental stage and has not been widely adopted.
## References
- [Nix Store - Nix Manual](https://nixos.org/manual/nix/stable/store/)

View File

@ -1,243 +0,0 @@
# 使用 S3 自定义二进制缓存托管
## 简介
一个关于如何使用 MinIO S3 服务器设置自己的 S3 Nix 二进制缓存的指南。
## Nix 中的软件如何存储?
可以在系统上安装同一软件包的多个版本,从而能够同时满足各种依赖链。这使得可以安装多个依赖于
同一个第三方软件包的软件包,但使用不同版本的它。为了实现这一点,所有软件包都安装在全局的
Nix 存储中,路径为 `/nix/store/`,然后通过符号链接到相应的位置。为了验证软件包的唯一性,其
整个目录被哈希,并将哈希放入软件包的主文件夹的名称中。从使用相同依赖软件版本构建的相同 Nix
表达式构建的每个软件包都会产生相同的哈希,无论它是在什么系统上构建的。如果任何依赖软件的版
本发生更改,这将导致最终软件包的新哈希。
使用符号链接来“安装”软件包并将所有正确的依赖项链接到它还使得原子更新成为可能。为了使这一点
更清楚,让我们来考虑一个例子,其中软件 X 安装在旧版本中并且应该进行更新。软件 X 安装在全局
Nix 存储中的自己的目录中,并符号链接到正确的目录,比如 `/usr/local/bin/`。当触发更新时X
的新版本会安装到全局 Nix 存储中,而不会干扰其旧版本。一旦安装完成,包括其所有依赖项在内的
最终软件包在 Nix 存储中,最后一步是将符号链接更改为 `/usr/local/bin/`。由于在 Unix 中创建
新的符号链接来覆盖旧的符号链接是一个原子操作,因此这个操作不可能失败并使软件包处于损坏状
态。唯一可能的问题是在符号链接创建之前或之后失败。无论哪种方式,结果都是我们要么有旧版本的
X要么有新安装的版本但中间没有任何东西。
引用自原作https://medium.com/earlybyte/the-s3-nix-cache-manual-e320da6b1a9b
## Nix 二进制缓存
无论 Nix 的每个方面听起来多么棒,它的设计也有一个主要缺点,那就是每次构建软件包都会触发整
个依赖链的构建过程。这可能需要相当长的时间,因为甚至像 gcc 或 ghc 这样的编译器都必须提前构
建。这样的构建过程可能会消耗大量内存,在受限平台(如树莓派)上使用它时会引入额外的障碍。
为了克服总是从头开始构建一切以及丢失对软件包预构建版本的访问权限的缺点,还可以使用 S3 服务
器(如 MinIO构建自己的 Nix 二进制缓存。
设置您的缓存,填充它的二进制文件并使用您新构建的缓存中的二进制文件的过程将被逐步描述。对于
本手册,我假设您已经有了 Nix并且在您的环境中已经运行了 MinIO 服务器。如果没有,您可以查
看 MinIO 的[官方部署指南](https://min.io/docs/minio/linux/operations/installation.html)。
您需要确保通过信任的证书以 `HTTPS` 方式访问 MinIO。在这里Let's Encrypt 将非常有用。
在本文中,让我们探讨如何自托管一个 S3 兼容服务器 MinIO 作为二进制缓存存储。
引用自原作https://medium.com/earlybyte/the-s3-nix-cache-manual-e320da6b1a9b
## 如何将 S3 用作二进制缓存服务器
### 先决条件
- 在您的环境中设置 MinIO。
- 拥有有效的 SSL 证书,可以是公共证书也可以是私有证书。在本教程中,我们将使用
`minio.homelab.local`(私有证书)的示例步骤。如果您计划使用私有证书,您必须自己解决 DNS
挑战。因此,建议使用公共证书。
- 在您的环境中安装 `minio-client`
### 生成密码
```bash
nix run nixpkgs#pwgen -- -c -n -y -s -B 32 1
# oenu1Yuch3rohz2ahveid0koo4giecho
```
### 设置 MinIO 客户端
安装 MinIO 命令行客户端 `mc`
```nix
{ pkgs, ... }:
{
environment.systemPackages = with pkgs; [
minio-client # 用于文件系统和对象存储的 ls、cp、mkdir、diff 和 rsync 命令的替代品
];
}
```
创建或编辑 `~/.mc/config.json`
```json
{
"version": "10",
"aliases": {
"s3": {
"url": "https://s3.homelab.local",
"accessKey": "minio",
"secretKey": "oenu1Yuch3rohz2ahveid0koo4giecho",
"api": "s3v4",
"path": "auto"
}
}
}
```
在我们用来构建 Nix 包的机器上,我们需要在一些地方使用 S3 凭据。设置一个文件
`~/.aws/credentials` 并用我们的 nixbuilder 用户的凭据填充它。用 `pwgen` 命令生成的密码替换
`<nixbuildersecret>`
```plaintext
[default]
aws_access_key_id=nixbuilder
aws_secret_access_key=<nixbuildersecret>
```
### 设置 S3 存储桶作为二进制缓存
创建 `nix-cache` 存储桶。
```bash
mc mb s3/nix-cache
```
创建 `nixbuilder` MinIO 用户并分配密码。
```bash
mc admin user add s3 nixbuilder <PASSWORD>
```
在当前工作目录中创建名为 `nix-cache-write.json` 的文件,并具有以下内容:
```json
{
"Id": "AuthenticatedWrite",
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AuthenticatedWrite",
"Action": [
"s3:AbortMultipartUpload",
"s3:GetBucketLocation",
"s3:GetObject",
"s3:ListBucket",
"s3:ListBucketMultipartUploads",
"s3:ListMultipartUploadParts",
"s3:PutObject"
],
"Effect": "Allow",
"Resource": ["arn:aws:s3:::nix-cache", "arn:aws:s3:::nix-cache/*"],
"Principal": "nixbuilder"
}
]
}
```
使用 `nix-cache-write.json` 创建允许 `nixbuilder` 上载文件到缓存的策略。
```bash
mc admin policy add s3 nix-cache-write nix-cache-write.json
```
将我们上面创建的策略与 `nixbuilder` 用户关联。
```bash
mc admin policy set s3 nix-cache-write user=nixbuilder
```
允许匿名用户在不进行身份验证的情况下下载文件。
```bash
mc anonymous set download s3/nix-cache
```
在工作目录中创建名为 `nix-cache-info` 的文件。此文件告诉 Nix 桶确实是一个二进制缓存。
```bash
cat > nix-cache-info <<EOF
StoreDir: /nix/store
WantMassQuery: 1
Priority: 40
EOF
```
`nix-cache-info` 复制到缓存桶。
```bash
mc cp ./nix-cache-info s3/nix-cache/nix-cache-info
```
### 生成密钥对
为签署存储路径生成一个密钥对。密钥名称是任意的,但 NixOS 开发人员强烈建议使用缓存的域名后
跟一个整数。如果密钥需要撤销或重新生成,可以递增尾部整数。
```bash
nix key generate-secret --key-name s3.homelab.local-1 > ~/.config/nix/secret.key
nix key convert-secret-to-public < ~/.config/nix/secret.key > ~/.config/nix/public.key
cat ~/.config/nix/public.key
# s3.homelab.local-1:m0J/oDlLEuG6ezc6MzmpLCN2MYjssO3NMIlr9JdxkTs=
```
### 使用 Flake 激活二进制缓存
将以下内容放入 `configuration.nix` 或您的任何自定义 NixOS 模块中:
```nix
{
nix = {
settings = {
# 在获取软件包时,替代器将被附加到默认的替代器。
extra-substituters = [
"https://s3.homelab.local/nix-cache/"
];
extra-trusted-public-keys = [
"s3.homelab.local-1:m0J/oDlLEuG6ezc6MzmpLCN2MYjssO3NMIlr9JdxkTs="
];
};
};
}
```
重新构建系统。
```bash
sudo nixos-rebuild switch --upgrade --flake .#<HOST>
```
### 推送路径到存储
对本地存储中的一些路径进行签名。
```bash
nix store sign --recursive --key-file ~/.config/nix/secret.key /run/current-system
```
将这些路径复制到缓存。
```bash
nix copy --to 's3://nix-cache?profile=nixbuilder&endpoint=s3.homelab.local' /run/current-system
```
### 添加自动对象到期策略
```bash
mc ilm rule add s3/nix-cache --expire-days "DAYS"
# 例如mc ilm rule add s3/nix-cache --expire-days "7"
```
### 参考资料
以下是我在编写本文档时使用的一些来源:
- [Jeff 的博客文章Nix 二进制缓存](https://jcollie.github.io/nixos/2022/04/27/nixos-binary-cache-2022.html)
- [NixOS wiki 上的二进制缓存](https://nixos.wiki/wiki/Binary_Cache)
- [NixOS 手册中关于通过 S3 提供 Nix 存储](https://nixos.org/manual/nix/stable/package-management/s3-substituter.html)
- [NixOS 手册中关于通过 HTTP 提供 Nix 存储](https://nixos.org/manual/nix/stable/package-management/binary-cache-substituter.html)

View File

@ -1,20 +1,19 @@
# 添加自定义缓存服务器 {#add-custom-cache-servers}
# 添加二进制缓存服务器
## 什么是 Nix 缓存服务器 {#what-is-nix-cache-server}
前面介绍了 Nix Store 与二进制缓存的概念,这里我们来看看如何添加多个缓存服务器,以加速包的
下载速度。
Nix 提供了官方缓存服务器 <https://cache.nixos.org>,它缓存了 nixpkgs 中所有 packages 在常
用 CPU 指令集下的构建结果,当你在本地执行 Nix 构建指令时,如果 Nix 在服务器中匹配到对应的
缓存,就会直接下载该缓存文件,跳过耗时的本地编译构建从而大大提升构建速度。
## 为什么要添加缓存服务器 {#why-add-cache-servers}
## 为什么要添加自定义缓存服务器 {#why-add-custom-cache-servers}
两个原因:
Nix 提供的官方缓存服务器 <https://cache.nixos.org> 提供了绝大部分常用软件包的二进制缓存,
但它并不能满足所有用户的需求。在以下情况下,我们会需要添加额外的缓存服务器:
1. 添加一些第三方项目的缓存服务器,例如 nix-community 的缓存服务器
<https://nix-community.cachix.org>,这可以大大提升这些第三方项目的构建速度
<https://nix-community.cachix.org> 提供了社区项目的二进制缓存,可以加速这些项目的构建
1. 添加离用户最近的缓存服务器镜像站,用于加速下载。
1. 添加自己搭建的缓存服务器,用于加速个人项目的构建速度。
## 如何添加自定义缓存服务器 {#how-to-add-custom-cache-servers}
## 如何添加缓存服务器 {#how-to-add-custom-cache-servers}
Nix 中通过如下几个 options 来配置缓存服务器:

View File

@ -0,0 +1,218 @@
# 使用 S3 自定义二进制缓存托管
## 简介
Nix 二进制缓存是 Nix Store 的一个实现,它不把数据存储在本地,而是存储在远程服务器上,方便
二进制缓存的多机共享。
Nix 官方的二进制缓存服务器只提供了使用标准参数构建的二进制缓存。如果你自定义了构建参数,或
者你使用了 Nixpkgs 之外的软件包,那就会导致 Nix 找不到对应的二进制缓存,从而执行本地构建流
程。
单纯依赖你本地的 Nix Store `/nix/store` 有时候会变得很痛苦,因为你需要在每台机器上重新构建
所有你自定义的这些软件包,这可能需要相当长的时间,而且构建过程可能会消耗大量内存。如果是在
Raspberry Pi 等性能较低的平台上使用 Nix这种情况会变得更加糟糕。
本文档将介绍如何使用 S3 服务(如 MinIO搭建你自己的 Nix 二进制缓存服务器,以便在多台机器
之间共享构建结果,从而解决上述问题。
## 准备工作
1. 一台 NixOS 主机
1. 部署好 MinIO 服务器
1. 如果没有,您可以参考 MinIO
的[官方部署指南](https://min.io/docs/minio/linux/operations/installation.html) 进行
部署。
1. MinIO 服务器需要具备有效的 TLS 数字证书,可以是公共证书也可以是私有证书。本文将使用
`https://minio.homelab.local` 加上私有证书作为示例。
1. 安装好 `minio-client`
## 生成密码
```bash
nix run nixpkgs#pwgen -- -c -n -y -s -B 32 1
# => oenu1Yuch3rohz2ahveid0koo4giecho
```
## 设置 MinIO 客户端
安装 MinIO 命令行客户端 `mc`
```nix
{ pkgs, ... }:
{
environment.systemPackages = with pkgs; [
minio-client # 用于文件系统和对象存储的 ls、cp、mkdir、diff 和 rsync 命令的替代品
];
}
```
创建 `~/.mc/config.json`,内容格式如下(注意将其中的关键参数替换为你自己的):
```json
{
"version": "10",
"aliases": {
"s3": {
"url": "https://s3.homelab.local",
"accessKey": "minio",
"secretKey": "oenu1Yuch3rohz2ahveid0koo4giecho",
"api": "s3v4",
"path": "auto"
}
}
}
```
Nix 将直接与 S3 存储桶交互,因此我们都需要给所有需要访问 Nix 二进制缓存的机器配置好对应的
S3 凭据。创建 `~/.aws/credentials`,内容如下(请注意用前面 `pwgen` 命令生成的密码替换
`<nixbuildersecret>`)。
```toml
[default]
aws_access_key_id=nixbuilder
aws_secret_access_key=<nixbuildersecret>
```
## 设置使用 S3 存储桶作为二进制缓存
先通过 minio 客户端创建 `nix-cache` 存储桶:
```bash
mc mb s3/nix-cache
```
创建 `nixbuilder` 这个 MinIO 用户并为其分配密码:
```bash
mc admin user add s3 nixbuilder <PASSWORD>
```
在当前工作目录中创建名为 `nix-cache-write.json` 的文件,内容如下:
```json
{
"Id": "AuthenticatedWrite",
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AuthenticatedWrite",
"Action": [
"s3:AbortMultipartUpload",
"s3:GetBucketLocation",
"s3:GetObject",
"s3:ListBucket",
"s3:ListBucketMultipartUploads",
"s3:ListMultipartUploadParts",
"s3:PutObject"
],
"Effect": "Allow",
"Resource": ["arn:aws:s3:::nix-cache", "arn:aws:s3:::nix-cache/*"],
"Principal": "nixbuilder"
}
]
}
```
现在再使用刚刚创建好的 `nix-cache-write.json` 文件创建一个上载文件到 S3 的策略:
```bash
mc admin policy add s3 nix-cache-write nix-cache-write.json
```
将我们上面创建的 S3 策略与 `nixbuilder` 用户关联:
```bash
mc admin policy set s3 nix-cache-write user=nixbuilder
```
再允许匿名用户在不进行身份验证的情况下下载文件,这样所有 Nix 服务器就都能直接从这个 S3 缓
存拉数据了:
```bash
mc anonymous set download s3/nix-cache
```
最后,添加 `nix-cache-info` 文件到 S3 桶根目录中Nix 需要这个文件记录一些二进制缓存相关的
信息:
```bash
cat > nix-cache-info <<EOF
StoreDir: /nix/store
WantMassQuery: 1
Priority: 40
EOF
# 将 `nix-cache-info` 复制到 S3 桶中
mc cp ./nix-cache-info s3/nix-cache/nix-cache-info
```
## 生成签名密钥对
前面介绍了Nix 二进制缓存使用公钥签名机制校验数据的数据来源与完整性,因此我们还需要为我们
的 Nix 构建机生成一个蜜钥对用于二进制缓存的签名验证。
密钥名称是任意的,但 NixOS 开发人员强烈建议使用缓存的域名后跟一个整数,这样如果密钥需要撤
销或重新生成,就可以递增末尾的整数。
```bash
nix key generate-secret --key-name s3.homelab.local-1 > ~/.config/nix/secret.key
nix key convert-secret-to-public < ~/.config/nix/secret.key > ~/.config/nix/public.key
cat ~/.config/nix/public.key
# => s3.homelab.local-1:m0J/oDlLEuG6ezc6MzmpLCN2MYjssO3NMIlr9JdxkTs=
```
## 在 `flake.nix` 中使用 S3 二进制缓存
将以下内容放入 `configuration.nix` 或您的任何自定义 NixOS 模块中:
```nix
{
nix = {
settings = {
# 在获取软件包时,替代器将被附加到默认的替代器。
extra-substituters = [
"https://s3.homelab.local/nix-cache/"
];
extra-trusted-public-keys = [
"s3.homelab.local-1:m0J/oDlLEuG6ezc6MzmpLCN2MYjssO3NMIlr9JdxkTs="
];
};
};
}
```
重新构建系统,就可以使用上我们创建好的 S3 二进制缓存了:
```bash
sudo nixos-rebuild switch --upgrade --flake .#<HOST>
```
## 推送存储路径到二进制缓存
对本地存储中的一些路径进行签名。
```bash
nix store sign --recursive --key-file ~/.config/nix/secret.key /run/current-system
```
将这些路径复制到缓存:
```bash
nix copy --to 's3://nix-cache?profile=nixbuilder&endpoint=s3.homelab.local' /run/current-system
```
## 添加自动对象过期策略
```bash
mc ilm rule add s3/nix-cache --expire-days "DAYS"
# 例如mc ilm rule add s3/nix-cache --expire-days "7"
```
## 参考
- [Blog post by Jeff on Nix binary caches](https://jcollie.github.io/nixos/2022/04/27/nixos-binary-cache-2022.html)
- [Binary cache in the NixOS wiki](https://nixos.wiki/wiki/Binary_Cache)
- [Serving a Nox store via S3 in the NixOS manual](https://nixos.org/manual/nix/stable/package-management/s3-substituter.html)
- [Serving a Nix store via HTTP in the NixOS manual](https://nixos.org/manual/nix/stable/package-management/binary-cache-substituter.html)

150
docs/zh/nix-store/intro.md Normal file
View File

@ -0,0 +1,150 @@
# Nix Store 与二进制缓存
这里我们先简单介绍下 Nix Store、Nix 二进制缓存以及其他相关概念,但不涉及具体的配置与使用方
法,这些内容会在后续章节中详细介绍。
## Nix Store
Nix Store 是 Nix 包管理器的核心概念之一,它是一个只读文件系统,用于存储所有需要不可变这一
特性的文件,包括软件包的构建结果、软件包的元数据、软件包的所有构建输入等等。
Nix 包管理器使用 Nix 函数式语言来描述软件包及其依赖关系,每个软件包都被视为一个纯函数的输
出,软件包的构建结果被保存在 Nix Store 中。
Nix Store 中的数据具有固定的路径格式:
```
/nix/store/b6gvzjyb2pg0kjfwrjmg1vfhh54ad73z-firefox-33.1
|--------| |------------------------------| |----------|
store directory digest name
```
可以看到Nix Store 中的路径以一个哈希值digest为前缀后面跟着软件包的名称和版本号。这
个哈希值是基于软件包的所有输入信息(构建参数、依赖关系、依赖版本等等)计算出来的,任何构建
参数或依赖关系的变化都会导致哈希值的变化,从而保证了每个软件包路径的唯一性。再加上 Nix
Store 是一个只读文件系统,这就保证了软件包的不可变性,即软件包一旦构建完成,就不会再发生变
化。
因为构建结果的存储路径是基于构建流程的所有输入信息计算出来的,**同样的输入信息会得到同样的
存储路径** 这种设计也被称为输入寻址模型_Input-addressed Model_
### NixOS 如何使用 Nix Store
NixOS 的声明式配置将会计算出哪些软件包需要被安装,然后将这些软件包在 Nix Store 中的存储路
径软链接到 `/run/current-system` 中,再通过修改 `PATH` 等环境变量指向
`/run/current-system` 中对应的文件夹从而实现软件包的安装。每次部署时NixOS 会计算出新的
系统配置,清理掉旧的软链接,再重新创建新的软链接,从而确保系统环境与声明式配置一致。
home-manager 也是类似的,它会将用户配置的软件包软链接到
`/etc/profiles/per-user/your-username` 这个路径下,再通过修改 `PATH` 等环境变量指向这个路
径,从而实现用户软件包的安装。
```bash
# 查看环境中的 bash 来自哪个路径(使用 NixOS 安装)
which bash
╭───┬─────────┬─────────────────────────────────┬──────────╮
│ # │ command │ path │ type │
├───┼─────────┼─────────────────────────────────┼──────────┤
│ 0 │ bash │ /run/current-system/sw/bin/bash │ external │
╰───┴─────────┴─────────────────────────────────┴──────────╯
ls -al /run/current-system/sw/bin/bash
lrwxrwxrwx 15 root root 76 1970年 1月 1日 /run/current-system/sw/bin/bash -> /nix/store/1zslabm02hi75anb2w8zjrqwzgs0vrs3-bash-interactive-5.2p26/bin/bash
# 查看环境中的 cowsay 来自哪个路径(使用 home-manager 安装)
which cowsay
╭───┬─────────┬────────────────────────────────────────┬──────────╮
│ # │ command │ path │ type │
├───┼─────────┼────────────────────────────────────────┼──────────┤
│ 0 │ cowsay │ /etc/profiles/per-user/ryan/bin/cowsay │ external │
╰───┴─────────┴────────────────────────────────────────┴──────────╯
ls -al /etc/profiles/per-user/ryan/bin/cowsay
lrwxrwxrwx 2 root root 72 1970年 1月 1日 /etc/profiles/per-user/ryan/bin/cowsay -> /nix/store/w2czyf82gxz4vy9kzsdhr88112bmc0c1-home-manager-path/bin/cowsay
```
`nix develop` 命令则是直接将软件包的存储路径添加到 `PATH` `LD_LIBRARY_PATH` 等环境变量
中,使新创建的 shell 环境中可以直接使用这些软件包或库。
以本书的源码仓库
[ryan4yin/nixos-and-flakes-book](https://github.com/ryan4yin/nixos-and-flakes-book) 为例,
在该仓库中执行 `nix develop` 命令,再查看下 `PATH` 环境变量的内容:
```bash
nix develop
node v20.9.0
env | egrep '^PATH'
PATH=/nix/store/h13fnmpm8m28qypsba2xysi8a90crphj-pre-commit-3.6.0/bin:/nix/store/2mqyvwp96d4jynsnzgacdk5rg1kx2a9a-node2nix-1.11.0/bin:/nix/store/a1hckfqzyys4rfgbdy5kmb5w0zdr55i5-nodejs-20.9.0/bin:/nix/store/gjrfcl2bhv7kbj883k7b18n2aprgv4rf-pnpm-8.10.2/bin:/nix/store/z6jfxqyj1wq62iv1gn5b5d9ms6qigkg0-yarn-1.22.19/bin:/nix/store/2k5irl2cfw5m37r3ibmpq4f7jndb41a8-prettier-3.0.3/bin:/nix/store/zrs710jpfn7ngy5z4c6rrwwjq33b2a0y-git-2.42.0/bin:/nix/store/dkmyyrkyl0racnhsaiyf7rxf43yxhx92-typos-1.16.23/bin:/nix/store/imli2in1nr1h8qh7zh62knygpl2zj66l-alejandra-3.0.0/bin:/nix/store/85jldj870vzcl72yz03labc93bwvqayx-patchelf-0.15.0/bin:/nix/store/90h6k8ylkgn81k10190v5c9ldyjpzgl9-gcc-wrapper-12.3.0/bin:/nix/store/hf2gy3km07d5m0p1lwmja0rg9wlnmyr7-gcc-12.3.0/bin:/nix/store/cx01qk0qyylvkgisbwc7d3pk8sliccgh-glibc-2.38-27-bin/bin:/nix/store/bblyj5b3ii8n6v4ra0nb37cmi3lf8rz9-coreutils-9.3/bin:/nix/store/1alqjnr40dsk7cl15l5sn5y2zdxidc1v-binutils-wrapper-2.40/bin:/nix/store/1fn92b0783crypjcxvdv6ycmvi27by0j-binutils-2.40/bin:/nix/store/bblyj5b3ii8n6v4ra0nb37cmi3lf8rz9-coreutils-9.3/bin:/nix/store/l974pi8a5yqjrjlzmg6apk0jwjv81yqw-findutils-4.9.0/bin:/nix/store/8q25nyfirzsng6p57yp8hsaldqqbc7dg-diffutils-3.10/bin:/nix/store/9c5qm297qnvwcf7j0gm01qrslbiqz8rs-gnused-4.9/bin:/nix/store/rx2wig5yhpbwhnqxdy4z7qivj9ln7fab-gnugrep-3.11/bin:/nix/store/7wfya2k95zib8jl0jk5hnbn856sqcgfk-gawk-5.2.2/bin:/nix/store/xpidksbd07in3nd4sjx79ybwwy81b338-gnutar-1.35/bin:/nix/store/202iqv4bd7lh6f7fpy48p7q4d96lqdp7-gzip-1.13/bin:/nix/store/ik7jardq92dxw3fnz3vmlcgi9c8dwwdq-bzip2-1.0.8-bin/bin:/nix/store/v4iswb5kwj33l46dyh2zqh0nkxxlr3mz-gnumake-4.4.1/bin:/nix/store/q1c2flcykgr4wwg5a6h450hxbk4ch589-bash-5.2-p15/bin:/nix/store/cbj1ph7zi009m53hxs90idl1f5i9i941-patch-2.7.6/bin:/nix/store/76z4cjs7jj45ixk12yy6k5z2q2djk2jb-xz-5.4.4-bin/bin:/nix/store/qmfxld7qhk8qxlkx1cm4bkplg1gh6jgj-file-5.45/bin:/home/ryan/.local/bin:/home/ryan/go/bin:/home/ryan/.config/emacs/bin:/home/ryan/.local/bin:/home/ryan/go/bin:/home/ryan/.config/emacs/bin:/nix/store/jsc6jydv5zjpb3dvh0lxw2dzxmv3im9l-kitty-0.32.1/bin:/nix/store/ihpdcszhj8bdmyr0ygvalqw9zagn0jjz-imagemagick-7.1.1-28/bin:/nix/store/2bm2yd5jqlwf6nghlyp7z88g28j9n8r0-ncurses-6.4-dev/bin:/run/wrappers/bin:/guix/current/bin:/home/ryan/.guix-home/profile/bin:/home/ryan/.guix-profile/bin:/home/ryan/.nix-profile/bin:/nix/profile/bin:/home/ryan/.local/state/nix/profile/bin:/etc/profiles/per-user/ryan/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin:/nix/store/c53f8hagyblvx52zylsnqcc0b3nxbrcl-binutils-wrapper-2.40/bin:/nix/store/fpagbmzdplgky01grwhxcsazvhynv1nz-pciutils-3.10.0/bin:/nix/store/4cjqvbp1jbkps185wl8qnbjpf8bdy8j9-gcc-wrapper-13.2.0/bin
```
显然 `nix develop` 将很多软件包的存储路径直接添加到了 `PATH` 环境变量中。
## Nix Store 的垃圾回收
Nix Store 是一个中心化的存储系统,所有的软件包构建输入跟输出都会被存储在这里。随着系统的使
Nix Store 中的软件包会越来越多,占用的磁盘空间也会越来越大。
为了避免 Nix Store 无限制地增长Nix 包管理器为本地 Nix Store 提供了垃圾回收机制,用于清理
`/nix/store` 中的旧数据、回收存储空间。
根据
[Chapter 11. The Garbage Collector - nix pills](https://nixos.org/guides/nix-pills/garbage-collector)
的说法, `nix-store --gc` 命令会执行垃圾回收操作,它会递归遍历 `/nix/var/nix/gcroots/`
录下的所有软链接,找出所有被引用的软件包,然后将不再被引用的软件包删除。而
`nix-collect-garbage --delete-old` 则更进一步,它会先删除掉所有旧的
[profiles](https://nixos.org/manual/nix/stable/command-ref/files/profiles),再执行
`nix-store --gc` 命令清理掉不再被引用的软件包。
需要注意的是,`nix build`, `nix develop` 等命令的构建结果并不会被自动添加到
`/nix/var/nix/gcroots/` 目录中,所以这些构建结果会被垃圾回收机制清理掉。你可以通过
`nix-instantiate``keep-outputs = true` 等手段来避免这种情况,但我目前觉得搭建一个自己
的二进制缓存服务器,然后在你在缓存服务器上配置一个较长的缓存时间(比如一年),将数据推送到
缓存服务器上,这样既可以在所有机器上共享构建结果,又可以避免本地构建结果被本地的垃圾回收机
制清理掉,一举两得。
## 二进制缓存
Nix 包管理器与 Nix Store 的设计保证了软件包的不可变性,使得 Nix Store 中的构建结果可以直接
被在多台机器之间共享。只要这些机器使用了同样的输入信息构建软件包,它们就会得到相同的输出路
Nix 则可以据此直接复用其他机器上的构建结果,而不需要重新构建软件包,从而提升软件包的安
装速度。
Nix 二进制缓存就是基于这个特性而设计的,它实质也是 Nix Store 的一个实现,只不过它不把数据
存储在本地而是存储在远程服务器上。需要使用的时候Nix 包管理器会从远程服务器上下载对应的
构建结果到本地的 `/nix/store` 中,避免耗时的本地构建。
Nix 提供了官方二进制缓存服务器 <https://cache.nixos.org>,它缓存了 nixpkgs 中绝大部分
packages 在常用 CPU 指令集下的构建结果。当你在本地执行 Nix 构建指令时Nix 首先会尝试从缓
存服务器中查找对应的二进制缓存,如果查找到了,就会直接下载该缓存文件,跳过耗时的本地编译构
建从而大大提升构建速度。
## Nix 二进制缓存的信任模型
**Input-addressed Model** **只保证同样的输入得到同样的输出路径,并不保证输出内容的唯一
性**。也就是说即使输入信息相同,多次构建同一个软件包得到的输出内容也可能不同。
虽然 Nix 已经通过在构建环境中默认禁用网络、使用固定的时间戳等方式尽量减少不确定性,但软件
包构建过程中仍然可能受到一些不可控因素的影响而产生不同的输出内容。这些不可控因素导致的输出
内容不同通常不会对软件包的功能产生影响,但却给二进制缓存的安全共享带来了挑战——输出内容的不
确定性使我们无法判断从缓存服务器中下载的二进制缓存是否真的是使用我们声明的输入信息构建的,
是否包含恶意内容。
Nix 包管理器目前给出的解决方案是——使用公私钥签名机制来验证二进制缓存的数据来源与完整性。这
种验证方式实际是将安全责任转嫁给了用户。用户如果希望使用某个非官方缓存服务器来加快某些软件
包的构建速度,那就必须将该缓存服务器的公钥添加进 `trusted-public-keys` 中,并自己承担对应
的安全风险——该缓存服务器提供的缓存数据可能夹带了私货(恶意内容)。
### Content-addressed model
[RFC062 - content-addressed store paths](https://github.com/NixOS/rfcs/blob/master/rfcs/0062-content-addressed-paths.md)
是社区在提升构建结果的一致性上的一次尝试。它提出了一种新的存储路径计算方式,即基于构建结果
outputs而不是输入信息inputs来计算最终的存储路径。这种设计可以保证构建结果的一致
性——如果构建结果不同,那么存储路径也会不同,从而避免了 input-addressed model 中存在的输出
内容不确定性问题。
不过它目前还在实验性阶段,尚未被广泛应用。
## 参考
- [Nix Store - Nix Manual](https://nixos.org/manual/nix/stable/store/)