在树莓派无头安装 NixOS

在树莓派上无显示器与键鼠安装 NixOS

原文标题:Installing headless NixOS on Raspberry Pi

原文地址:https://blog.krishu.moe/posts/nixos-raspberry-pi/

原文作者:Kris Hu


确实,在安装过程中我需要使用显示器来进行调试。不过,既然我已经解决了所有的问题,你可能只需要一个U盘和一根网线就可以完成这个操作了。

简而言之: 构建一个带有 ssh 的定制版 NixOS 镜像作为安装工具。

# 亮点

  • 无头 NixOS 安装:即全程无需树莓派连接显示器或键盘。
  • 使用 tmpfs 挂载根目录
  • 使用 btrfs 进行持久化存储

# 要求

  • 树莓派 3/4:这两个是官方主线支持的。1 我是用的是 Raspberry Pi 4,不过 rpi3 应当也可以适用该安装方法🤔
  • 安装有官方 Raspberry Pi OS 的 SD 卡
  • 一块 U 盘:确保上面没有重要数据,因为安装过程中需要完全擦除
  • 可选:外接 USB 固态硬盘。

# 设置树莓派从 USB 启动

首先,需更新树莓派上的固件以实现从 USB 设备启动。2

  1. 在 Raspberry Pi OS 上运行 sudo rpi-eeprom-update -a 然后重启。确保固件版本为 2020 年 9 月 3 日及以后的版本。
  2. 运行 sudo -E rpi-eeprom-config --edit 然后将 BOOT_ORDER 这一项改为 BOOT_ORDER=0xf14 ,其中
  • 从右向左,依次为
  • 4 = 尝试 USB 设备
  • 1 = 尝试 SD 卡
  • f = 无法启动时重新开始尝试
  1. sudo reboot 以应用所有的更改。

# 构建 NixOS 安装镜像

在撰写本文时(2024 年 4 月 12 日),官方 Hydra 构建的镜像,不论是 23.11unstable 还是 带有最新内核的 unstable 版本都无法在树莓派上成功启动。你只能看到指示灯两次长闪,随后两次短闪,而此后,不会有任何事情发生。因此,无论如何你都需要使用 linux_rpi4 3 来构建自定义镜像。

这是 hosts/nixos-pi-installer/default.nix 最关键的部分 4

 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 中设置以下内容,描述了我的编译机器,从而在我的家里云服务器上跨平台编译:5

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 上完成了这些事。6

使用 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。幸运的是,我们构建的安装镜像已经包含了我们需要的所有文件。78

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" ];

# 引用

使用 Hugo 构建
主题 StackJimmy 设计