原文标题:Installing headless NixOS on Raspberry Pi
原文地址:https://blog.krishu.moe/posts/nixos-raspberry-pi/
原文作者:Kris Hu
确实,在安装过程中我需要使用显示器来进行调试。不过,既然我已经解决了所有的问题,你可能 只需要一个U盘和一根网线就可以完成这个操作了。
简而言之: 构建一个带有 ssh 的定制版 NixOS 镜像作为安装工具。
亮点 无头 NixOS 安装:即全程无需树莓派连接显示器或键盘。 使用 tmpfs 挂载根目录 使用 btrfs 进行持久化存储
要求 树莓派 3/4:这两个是官方主线支持的。 我是用的是 Raspberry Pi 4,不过 rpi3 应当也可以适用该安装方法🤔 安装有官方 Raspberry Pi OS 的 SD 卡 一块 U 盘:确保上面没有重要数据,因为安装过程中需要完全擦除 可选:外接 USB 固态硬盘。
设置树莓派从 USB 启动 首先,需更新树莓派上的固件以实现从 USB 设备启动。
在 Raspberry Pi OS 上运行 sudo rpi-eeprom-update -a
然后重启。确保固件版本为 2020 年 9 月 3 日及以后的版本。 运行 sudo -E rpi-eeprom-config --edit
然后将 BOOT_ORDER
这一项改为 BOOT_ORDER=0xf14
,其中 从右向左,依次为 4
= 尝试 USB 设备1
= 尝试 SD 卡f
= 无法启动时重新开始尝试sudo reboot
以应用所有的更改。
构建 NixOS 安装镜像 在撰写本文时(2024 年 4 月 12 日),官方 Hydra 构建的镜像,不论是 23.11 ,unstable 还是 带有最新内核的 unstable 版本都无法在树莓派上成功启动。你只能看到指示灯两次长闪,随后两次短闪,而此后,不会有任何事情发生。因此,无论如何你都需要使用 linux_rpi4 来构建自定义镜像。
这是 hosts/nixos-pi-installer/default.nix
最关键的部分
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
{ config , lib , pkgs , inputs , outputs , modulesPath , ... }:
{
imports = [
inputs . nixos-hardware . nixosModules . raspberry-pi-4
( modulesPath + "/installer/sd-card/sd-image-aarch64.nix" )
];
nixpkgs . overlays = [
( final : super : {
makeModulesClosure = x :
super . makeModulesClosure ( x // { allowMissing = true ; });
})
];
boot . kernelPackages = lib . mkForce pkgs . linuxKernel . packages . linux_rpi4 ;
boot . supportedFilesystems = lib . mkForce [ "vfat" "btrfs" "tmpfs" ];
sdImage . compressImage = false ;
networking . hostName = "nixos-pi" ;
services . openssh . enable = true ;
security . sudo . wheelNeedsPassword = false ;
users . users . kris = {
isNormalUser = true ;
extraGroups = [ "wheel" ];
openssh . authorizedKeys . keys = [
# TODO: replace this with your own SSH public key
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDFfI9C3SaPCw2+K08jAs7B6CsQiIhF+H3oODy8WJf3 [email protected] "
];
};
system . stateVersion = "23.11" ;
nixpkgs . hostPlatform = "aarch64-linux" ;
}
这是 flake.nix
的一份实例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
description = "Kris' NixOS Flake" ;
inputs = {
nixpkgs . url = "github:NixOS/nixpkgs/nixos-23.11" ;
nixos-hardware . url = "github:NixOS/nixos-hardware/master" ;
};
outputs = { self , nixpkgs , ... } @ inputs :
let
inherit ( self ) outputs ;
in
{
nixosConfigurations = {
nixos-pi-installer = nixpkgs . lib . nixosSystem {
specialArgs = { inherit inputs outputs ; };
modules = [ ./hosts/nixos-pi-installer ];
};
};
};
}
我通过在 hosts/nixos-kris/default.nix
中设置以下内容,描述了我的编译机器,从而在我的家里云服务器上跨平台编译:
1
boot . binfmt . emulatedSystems = [ "aarch64-linux" ];
运行下面的命令以编译镜像:
1
nix build .#nixosConfigurations.nixos-pi-installer.config.system.build.sdImage --show-trace -L -v
然后你应该能在 result/sd-image
目录下获得一个 .img
文件。
烧录镜像到 U 盘 我在我的 Mac 上完成了这些事。
使用 diskutil list
来定位你的 U 盘 /dev/disk(n) (external, physical)
。然后运行下面的命令:
1
2
3
diskutil unmountDisk /dev/disk( n)
sudo dd if = ./nixos-sd-image-23.11.*-aarch64-linux.img of = /dev/rdisk( n) bs = 1m status = progress
diskutil eject /dev/disk( n)
完成后,我们将得到一个自己的 NixOS 安装 U 盘。
编写在树莓派上运行的 NixOS 配置 接下来的这部分非常重要:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{ lib , inputs , pkgs , ... }:
{
imports = [
inputs . nixos-hardware . nixosModules . raspberry-pi-4
];
nixpkgs . overlays = [
( final : super : {
makeModulesClosure = x :
super . makeModulesClosure ( x // { allowMissing = true ; });
})
];
hardware . raspberry-pi . "4" . fkms-3d . enable = true ;
environment . systemPackages = with pkgs ; [
libraspberrypi
];
networking . networkmanager . wifi . powersave = false ;
boot . kernelPackages = lib . mkForce pkgs . linuxKernel . packages . linux_rpi4 ;
boot . supportedFilesystems = lib . mkForce [ "vfat" "btrfs" "tmpfs" ];
}
关于 fileSystems
这部分,请参阅下面的 分区 部分。参见 NixOS Wiki 上的持久化 (EN) 一文以在使用 tmpfs 作为根目录的系统上的进行数据持久化。
在树莓派上安装 NixOS 首先,sudo poweroff
你的树莓派。然后插入 U 盘,当然也别忘记插入一根网线。然后插电,开机。你可能需要等待几分钟,因为安装程序在第一次启动时会清理文件系统。最后,你应当能在你的网络上发现你的树莓派🥳
一旦你找到了它,只需要 ssh
登录上它,然后就能执行接下来的安装流程。
在我的配置中,/dev/sda
是安装程序所在盘,/dev/sdb
是我目标安装盘,一块 USB M.2 SSD。在你的情况下,这可能有所不同。例如,如果你想安装到 SD 卡上, /dev/mmcblk0
则是你的目标盘。所以请根据你的实际情况对下面的命令做适当调整。
分区 1
2
3
4
5
6
7
8
9
10
11
12
13
14
wipefs -a /dev/sdb
parted /dev/sdb -- mklabel msdos
parted /dev/sdb -- mkpart primary fat32 1M 512M
parted /dev/sdb -- set 1 boot on
parted /dev/sdb -- mkpart primary btrfs 512MiB 100%
mkfs.vfat -F32 -n BOOT /dev/sdb1
mkfs.btrfs -L NIXOS /dev/sdb2 -f
mkdir -p /mnt/{ boot,nix,swap}
mount /dev/sdb1 /mnt/boot
mount -o compress = zstd /dev/sdb2 /mnt/nix
btrfs subvolume create /mnt/nix/swap
mount -o noatime,subvol= swap /dev/sdb2 /mnt/swap
btrfs filesystem mkswapfile --size 4g /mnt/swap/swapfile
这符合我的 hosts/nixos-pi/hardware-configuration.nix
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{
fileSystems . "/" =
{
device = "none" ;
fsType = "tmpfs" ;
options = [ "relatime" "mode=755" "size=75%" ];
};
fileSystems . "/boot" =
{
device = "/dev/disk/by-label/BOOT" ;
fsType = "vfat" ;
};
fileSystems . "/nix" =
{
device = "/dev/disk/by-label/NIXOS" ;
fsType = "btrfs" ;
options = [ "compress=zstd" ];
};
fileSystems . "/swap" =
{
device = "/dev/disk/by-label/NIXOS" ;
fsType = "btrfs" ;
options = [ "noatime" "subvol=swap" ];
};
swapDevices = [{ device = "/swap/swapfile" ; }];
}
安装 NixOS 本体 1
2
3
4
5
6
mkdir -p /mnt/etc/nixos
mkdir -p /mnt/nix/persist/etc/nixos
mount -o bind /mnt/nix/persist/etc/nixos /mnt/etc/nixos
git clone ( redacted) /mnt/etc/nixos
nixos-install --flake /mnt/etc/nixos#nixos-pi --no-root-password
复制固件 我们需要在第一个分区中安装 U-Boot 。幸运的是,我们构建的安装镜像已经包含了我们需要的所有文件。
1
2
3
mkdir /firmware
mount /dev/sda1 /firmware
cp /firmware/* /mnt/boot
重启以进入新 NixOS 好耶!现在你已经成功安装了 NixOS 了🥳。现在 poweroff
树莓派,断开安装程序所在的 U 盘,重新连接电源线。它应该可以很快启动到新安装的 NixOS。
附加:树莓派上 NixOS 的额外配置
蓝牙 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
hardware = {
bluetooth . enable = true ;
bluetooth . powerOnBoot = true ;
deviceTree . filter = "bcm2711-rpi-4*.dtb" ;
deviceTree . overlays = [
{
name = "bluetooth-overlay" ;
dtsText = ''
/dts-v1/;
/plugin/;
/ {
compatible = "brcm,bcm2711";
fragment@0 {
target = <&uart0_pins>;
__overlay__ {
brcm,pins = <30 31 32 33>;
brcm,pull = <2 0 0 2>;
};
};
};
'' ;
}
];
};
services . blueman . enable = true ;
systemd . services . btattach = {
before = [ "bluetooth.service" ];
after = [ "dev-ttyAMA0.device" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStart = " ${ pkgs . bluez } /bin/btattach -B /dev/ttyAMA0 -P bcm -S 3000000" ;
};
};
HDMI 上的声音 hardware.raspberry-pi."4".audio.enable = true;
无法正常启用 🥲
1
2
3
sound . enable = true ;
hardware . pulseaudio . enable = true ;
boot . kernelParams = [ "snd_bcm2835.enable_hdmi=1" ];
引用