我如何以宣告式安裝 NixOS
在我的其中一次網路儲存電腦組裝中,我在尋找 Flatcar Container Linux 的替代方案,並再次嘗試了 NixOS(距離上次使用已將近十年)。安裝 NixOS 的方式有很多種,在本文中,我將說明我如何在實體硬體或虛擬機器上以自己偏好的方式安裝 NixOS:透過網路並以完全宣告式的方式。
前言:何謂宣告式?
declarative(宣告式)一詞指的是你描述「要達成什麼」,而非「如何達成」。以 NixOS 來說,這表示你要宣告希望系統包含哪些軟體(加入設定選項 environment.systemPackages,或啟用某個模組),而不是例如執行 apt install。
declarative 方法的一個優點是,系統會遵循你的設定,因此只要還原設定變更,就能乾淨地還原系統上的變更。
我喜歡將 declarative 設定檔納入版本控制來管理,通常是使用 Git。
當初架設目前這套網路儲存系統時,我選擇了 CoreOS(後來的 Flatcar Container Linux),因為它是一個會自動更新的基礎系統,並具備 declarative 的 cloud-init 設定。
安裝 NixOS 的方式
圖形化安裝程式:僅適用於桌面環境
NixOS 手冊的「Installation」章節描述了圖形化安裝程式(「供桌面使用者使用」,以 Calamares 系統安裝程式為基礎,於 2022 年新增)以及手動安裝程式。
使用圖形化安裝程式時,很容易就能將 NixOS 安裝到磁碟上:只要不斷確認預設值,最後就會得到一個可運作的系統。但也有一些缺點:
- 安裝完成後,你需要手動啟用 SSH——而且必須在本機操作,無法透過網路進行。
- 圖形化安裝程式會為你產生一份初始的 NixOS 設定,但沒有辦法注入你自己的初始 NixOS 設定。
顯然,圖形化安裝程式並非為遠端安裝或自動化安裝而設計。
手動安裝
另一方面,手動安裝程式對我來說太過手動:可以展開NixOS 手冊「Installation summary」章節中的「Example 2」與「Example 3」來了解其過程。平心而論,這些步驟完全可行,但我不想在趕時間時用這種方式安裝系統。一來,手動流程在壓力下容易出錯。再者,互動式地複製貼上指令,根本就與撰寫 declarative 設定檔背道而馳。
網路安裝:nixos-anywhere
理想上,我希望大部分的安裝工作都能在自己的電腦上舒適地完成,這表示安裝程式必須能透過網路使用。此外,我希望機器在安裝完成後能立即以可運作的初始 NixOS 設定啟動(不需要任何手動步驟!)。
幸好,有一個(由社群提供的)解決方案:nixos-anywhere。你只需負責啟動 NixOS 安裝程式,然後執行一道指令,nixos-anywhere 就會透過 SSH 連入該安裝程式、分割你的磁碟並將 NixOS 安裝到磁碟上。值得注意的是,nixos-anywhere 是以 declarative 方式設定的,因此你可以隨時重複這個步驟。
(我知道 nixos-anywhere 甚至可以透過 SSH 連入任意系統並以 kexec 重新啟動至 NixOS 安裝程式,這當然是個很酷的炫技手法,但我更偏好明確啟動安裝程式的方式,因為在我看來這樣風險較低,也更通用、更可重複。)
設定:安裝 Nix
我想在其中一台機器上使用 NixOS,但(目前)不是在我的主要桌機上。
因此,我只在 Arch Linux 上安裝了 nix 工具(用於建置,即使不執行 NixOS):
% sudo pacman -S nix
% sudo groupadd -r nixbld
% for n in $(seq 1 24); do sudo useradd -c "Nix build user $n" \
-d /var/empty -g nixbld -G nixbld -M -N -r -s "$(which nologin)" \
nixbld$n; done
% sudo systemctl enable --now nix-daemon.socket現在,執行 nix-shell -p hello 應該會讓你進入一個已安裝 GNU hello 套件的新 shell:
% export NIX_PATH=nixpkgs=channel:nixos-25.05
% nix-shell -p hello
hello
[nix-shell:/tmp]$ hello
Hello, world!順帶一提,Arch Linux wiki 上的 Nix 頁面說明了如何使用 nix 安裝套件,但那不是我感興趣的部分:我只想遠端管理 NixOS 系統。
自行建置安裝程式
前面我說過「你只需負責啟動 NixOS 安裝程式」,這其實很簡單:將 ISO 映像檔寫入 USB 隨身碟並用它來啟動你的機器(或選擇 ISO 並啟動你的虛擬機器)。
但在能透過 SSH 遠端登入之前,我們需要手動設定密碼。我還需要以 TERM=xterm 環境變數來 SSH,因為 rxvt-unicode(我偏好的終端機)的 termcap 檔並未包含在預設的 NixOS 安裝程式環境中。同樣地,我設定的 locale 也無法運作,而我偏好的 shell(Zsh)也無法使用。
如果安裝程式能預先設定好一個方便的環境,不是會好得多嗎?
對於其他 Linux 發行版,像是 Debian、Fedora 或 Arch Linux,我不會嘗試重新建置官方的安裝程式 ISO 映像檔。我相信它們的流程與工具運作得很好,但我也確定那會是我需要額外學習、除錯與維護的東西。
但建置 NixOS 安裝程式與設定一般的 NixOS 系統非常相似:同樣的設定,同樣的建置工具。相關流程記載於官方 NixOS wiki。
我複製了通常會放進 configuration.nix 的客製化設定,匯入了來自 nixpkgs 的 installation-cd-minimal.nix 模組,並將結果放入 iso.nix 檔案中:
{ config, pkgs, ... }:
{
imports = [
<nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix>
<nixpkgs/nixos/modules/installer/cd-dvd/channel.nix>
];
i18n.supportedLocales = [
"en_DK.UTF-8/UTF-8"
"de_DE.UTF-8/UTF-8"
"de_CH.UTF-8/UTF-8"
"en_US.UTF-8/UTF-8"
];
i18n.defaultLocale = "en_US.UTF-8";
security.sudo.wheelNeedsPassword = false;
users.users.michael = {
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5secret"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5key"
];
isNormalUser = true;
description = "Michael Stapelberg";
extraGroups = [ "wheel" ];
initialPassword = "SGZ3odMZIesxTuh2Y2pUaJA"; # random for this post
shell = pkgs.zsh;
packages = with pkgs; [];
};
environment.systemPackages = with pkgs; [
git # for checking out github.com/stapelberg/configfiles
rsync
zsh
vim
emacs
wget
curl
rxvt-unicode # for terminfo
lshw
];
programs.zsh.enable = true;
services.openssh.enable = true;
# This value determines the NixOS release from which the default
# settings for stateful data, like file locations and database versions
# on your system were taken. It‘s perfectly fine and recommended to leave
# this value at the release version of the first install of this system.
# Before changing this value read the documentation for this option
# (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
system.stateVersion = "25.05"; # Did you read the comment?
}要建置 ISO 映像檔,我將 NIX_PATH 環境變數設為指向 nix-build(1) 的 iso.nix 檔案,並選擇 NixOS 25.05 的上游 channel:
% export NIX_PATH=nixos-config=$PWD/iso.nix:nixpkgs=channel:nixos-25.05
% nix-build '<nixpkgs/nixos>' -A config.system.build.isoImage在我的2025 年高階 Linux 電腦上花了約 1.5 分鐘後,安裝程式 ISO 就會出現在 result/iso/nixos-minimal-25.05.802216.55d1f923c480-x86_64-linux.iso(以我的情況來說大小為 1.46 GB)。
啟用 Nix Flakes
可惜的是,nix 專案至今仍未預設啟用「實驗性」的新命令列介面(CLI),儘管它已經推出超過 5 年,因此我們需要建立一個設定檔並啟用現代的 nix-command 介面:
% mkdir -p ~/.config/nix
% echo 'experimental-features = nix-command flakes' >> ~/.config/nix/nix.conf要如何分辨新舊指令?舊指令是用連字號連接的(nix-build),新指令則是以空白分隔的(nix build)。
你會注意到我也啟用了 Nix flakes,我使用它來讓我的 nix 建置是 hermetic 且固定在特定版本的 nixpkgs 以及我想包含在建置中的任何其他 nix 模組上。我喜歡將 flakes 比作其他程式設計環境中的版本鎖定檔:其概念是,5 個月後再建置系統,結果會與今天相同。
要驗證 flakes 是否可運作,請執行 nix shell(不是 nix-shell):
% nix shell nixpkgs#hello
/tmp 2 % hello
Hello, world!(重新)安裝步驟
供參考,以下是我在 Proxmox 中為 NixOS 建立新虛擬機器的設定。最重要的設定是 bios=ovmf(= UEFI 開機,不是預設值),這樣我才能在實體機器和虛擬機器上使用相同的開機載入程式設定:

在啟動我們(未簽署的)安裝程式之前,我們需要進入 UEFI 設定並停用 Secure Boot。舉例來說,Proxmox 預設就會啟用 Secure Boot。
接著,在目標系統上啟動客製化的安裝程式 ISO,並確認 ssh [email protected] 可以在不要求輸入密碼的情況下運作。
宣告一個 flake.nix,內容如下:
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-25.05";
disko.url = "github:nix-community/disko";
# Use the same version as nixpkgs
disko.inputs.nixpkgs.follows = "nixpkgs";
};
outputs =
{
nixpkgs,
disko,
...
}:
let
system = "x86_64-linux";
pkgs = import nixpkgs {
inherit system;
config.allowUnfree = false;
};
in
{
nixosConfigurations.zammadn = nixpkgs.lib.nixosSystem {
inherit system;
inherit pkgs;
modules = [
disko.nixosModules.disko
./configuration.nix
];
};
formatter.${system} = pkgs.nixfmt-tree;
};
}在 disk-config.nix 中宣告你的磁碟設定:
disk-config.nix
{ lib, ... }:
{
disko.devices = {
disk = {
main = {
device = lib.mkDefault "/dev/sda";
type = "disk";
content = {
type = "gpt";
partitions = {
ESP = {
type = "EF00";
size = "500M";
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
mountOptions = [ "umask=0077" ];
};
};
root = {
size = "100%";
content = {
type = "filesystem";
format = "ext4";
mountpoint = "/";
};
};
};
};
};
};
};
}在 configuration.nix 中宣告你想要的 NixOS 設定:
{ modulesPath, lib, pkgs, ... }:
{
imports =
[
(modulesPath + "/installer/scan/not-detected.nix")
./hardware-configuration.nix
./disk-config.nix
];
# Adding michael as trusted user means
# we can upgrade the system via SSH (see Makefile).
nix.settings.trusted-users = [ "michael" "root" ];
# Clean the Nix store every week.
nix.gc = {
automatic = true;
dates = "weekly";
options = "--delete-older-than 7d";
};
boot.loader.systemd-boot = {
enable = true;
configurationLimit = 10;
};
boot.loader.efi.canTouchEfiVariables = true;
networking.hostName = "zammadn";
time.timeZone = "Europe/Zurich";
# Use systemd for networking
services.resolved.enable = true;
networking.useDHCP = false;
systemd.network.enable = true;
systemd.network.networks."10-e" = {
matchConfig.Name = "e*"; # enp9s0 (10G) or enp8s0 (1G)
networkConfig = {
IPv6AcceptRA = true;
DHCP = "yes";
};
};
i18n.supportedLocales = [
"en_DK.UTF-8/UTF-8"
"de_DE.UTF-8/UTF-8"
"de_CH.UTF-8/UTF-8"
"en_US.UTF-8/UTF-8"
];
i18n.defaultLocale = "en_US.UTF-8";
users.mutableUsers = false;
security.sudo.wheelNeedsPassword = false;
users.users.michael = {
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5secret"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5key"
];
isNormalUser = true;
description = "Michael Stapelberg";
extraGroups = [ "networkmanager" "wheel" ];
initialPassword = "install"; # TODO: change!
shell = pkgs.zsh;
packages = with pkgs; [];
};
environment.systemPackages = with pkgs; [
git # for checking out github.com/stapelberg/configfiles
rsync
zsh
vim
emacs
wget
curl
];
programs.zsh.enable = true;
services.openssh.enable = true;
# This value determines the NixOS release from which the default
# settings for stateful data, like file locations and database versions
# on your system were taken. It‘s perfectly fine and recommended to leave
# this value at the release version of the first install of this system.
# Before changing this value read the documentation for this option
# (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
system.stateVersion = "25.05"; # Did you read the comment?
}…然後將其鎖定:
% nix flake lock- 使用 nixos-anywhere,從安裝程式取得 hardware-configuration.nix 並將 NixOS 安裝到磁碟:
% nix run github:nix-community/nixos-anywhere -- \
--flake .#zammadn \
--generate-hardware-config nixos-generate-config ./hardware-configuration.nix \
--target-host [email protected]約一分鐘後,我的虛擬機器就安裝完成並重新啟動了!
完整 nixos-anywhere 安裝紀錄(如果你好奇的話)
% nix run github:nix-community/nixos-anywhere -- \
--flake .#wiki \
--generate-hardware-config nixos-generate-config ./hardware-configuration.nix \
--target-host [email protected]
[... transcript truncated for brevity but contains full nixos-anywhere log ...]
### Installing NixOS ###
installing the boot loader...
setting up /etc...
Created "/boot/EFI".
installation finished!
### Rebooting ###
### Done! ###安裝後步驟
現在系統中 declarative 的部分已經就緒,我們需要處理有狀態(stateful)的部分。
以我的情況來說,唯一需要設定的有狀態部分是 Tailscale mesh VPN。
要設定 Tailscale,我透過 SSH 登入並執行 sudo tailscale up。接著,我依照連結將新節點加入我的網路。之後,在 Tailscale Machines 控制台中,我停用金鑰到期並加入 ACL 標籤。
進行變更
現在,在我修改了設定檔中的某些內容後,我會透過遠端使用 nixos-rebuild 將變更部署到我的 NixOS 系統:
% nix run nixpkgs#nixos-rebuild -- \
--target-host michael@zammadn \
--use-remote-sudo \
switch \
--flake .#zammadn請注意,並非所有變更都會在 nixos-rebuild switch 過程中完全套用:雖然 systemd 服務通常會重新啟動,但新需要的核心模組並不會自動載入(例如在 Frigate 中啟用 edgetpu coral 硬體加速器後)。
因此,為確保所有變更都已生效,請在部署變更後重新啟動(reboot)你的系統。
NixOS 的優點之一是,你可以在開機選單中選擇要執行哪一代的系統。如果最新的變更造成問題,你可以快速重新啟動至上一代來還原變更。當然,你也可以還原設定變更並部署新的一代——視當下情況選擇較方便的方式即可。
結論
透過本文,我希望能傳達當初剛開始使用 Nix 與 NixOS 時,希望有人能告訴我的事情:
- 啟用 flakes 與新的 CLI。
- 使用 nixos-anywhere 進行遠端安裝。
- 如果想要的話,可以自行建置安裝程式,這很簡單!
- 使用
nixos-rebuild內建的--target-host旗標進行遠端部署。
接下來該往哪裡去?
- 閱讀 nixos.org → Learn 上的所有文件。
- 這裡有幾篇來自與我同溫層的人所寫的文章,我曾參考作為靈感/參考資料,順序不分先後:
- Michael Lynch(麥可·林奇)寫了在 Oracle Cloud VM 上設定 NixOS以及管理他的 Zig 設定的文章。
- Nelson Elhage(尼爾森·艾爾哈吉)寫了關於使用 Nix 測試數十種 Python 直譯器的文章,作為他對 Python 3.14 tail-call 直譯器效能研究的一部分。
- Vincent Bernat(文森·伯納)寫了使用 Nix 為 ARM 單板電腦建置 SD 卡映像檔的文章。
- Mitchell Hashimoto(米契爾·哈西莫托)分享了他詳盡的 NixOS 設定。
- Wolfgang(沃夫岡)有一部關於為家用伺服器使用 NixOS 的 YouTube 影片(→ 他的設定)
- 聯繫你當地的 Nix 社群!我最近參加了 Nix Zürich 社群的「Zero Hydra Failures」活動,那裡親切的人們很樂意聊聊所有與 Nix 相關的話題 :)
隨機一篇部落格