├── .envrc ├── nixos └── pihole │ ├── requirements.txt │ ├── dhcp-helper │ └── Dockerfile │ ├── docker-compose.dhcpd.yml │ ├── docker-compose.cloudflared.yml │ ├── .example.env │ ├── docker-compose.yml │ ├── docker-compose.pihole.yml │ └── README.md ├── .gitignore ├── renovate.json ├── shell.nix ├── configuration.sdImage.nix ├── configuration.default.sdImage.nix ├── flake.nix ├── .github └── workflows │ ├── nix-build-using-docker.yaml │ └── nix-build-using-debian.yaml ├── configuration.default.nix ├── flake.lock ├── configuration.nix └── README.md /.envrc: -------------------------------------------------------------------------------- 1 | use_nix -------------------------------------------------------------------------------- /nixos/pihole/requirements.txt: -------------------------------------------------------------------------------- 1 | docker-compose 2 | -------------------------------------------------------------------------------- /nixos/pihole/dhcp-helper/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | RUN apk --no-cache add dhcp-helper 3 | EXPOSE 67 67/udp 4 | ENTRYPOINT ["dhcp-helper", "-n"] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .venv 2 | backup 3 | etc-dnsmasq.d 4 | etc-pihole 5 | 6 | .env 7 | docker-compose.override.yml 8 | result 9 | output 10 | .direnv 11 | .DS_Store 12 | sd-image.* 13 | 14 | .idea -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "nix": { 4 | "enabled": true 5 | }, 6 | "lockFileMaintenance": { 7 | "enabled": true 8 | }, 9 | "extends": [ 10 | "config:base" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | (import 2 | ( 3 | let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in 4 | fetchTarball { 5 | url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; 6 | sha256 = lock.nodes.flake-compat.locked.narHash; 7 | } 8 | ) 9 | { src = ./.; } 10 | ).shellNix 11 | -------------------------------------------------------------------------------- /configuration.sdImage.nix: -------------------------------------------------------------------------------- 1 | { config, pkgs, lib, ... }: 2 | { 3 | 4 | imports = [ 5 | 6 | 7 | # For nixpkgs cache 8 | 9 | 10 | # main configuration 11 | ./configuration.nix 12 | ]; 13 | 14 | sdImage.compressImage = false; 15 | 16 | system.copySystemConfiguration = true; 17 | } 18 | -------------------------------------------------------------------------------- /configuration.default.sdImage.nix: -------------------------------------------------------------------------------- 1 | { config, pkgs, lib, ... }: 2 | { 3 | 4 | imports = [ 5 | 6 | 7 | # For nixpkgs cache 8 | 9 | 10 | # main configuration 11 | ./configuration.default.nix 12 | ]; 13 | 14 | sdImage.compressImage = true; 15 | 16 | system.copySystemConfiguration = true; 17 | } 18 | -------------------------------------------------------------------------------- /nixos/pihole/docker-compose.dhcpd.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | networks: 3 | pihole: 4 | ipam: 5 | config: 6 | - subnet: ${DOCKER_SUBNET} 7 | # More info at https://github.com/pi-hole/docker-pi-hole/ and https://docs.pi-hole.net/ 8 | services: 9 | 10 | dhcphelper: 11 | build: ./dhcp-helper 12 | restart: unless-stopped 13 | # network_mode: "host" 14 | command: -s ${PIHOLE_PRIVATE_ADDRESS} 15 | ports: 16 | - "${PIHOLE_DHCPD_EXPOSE_PORT}:67/udp" 17 | cap_add: 18 | - NET_ADMIN -------------------------------------------------------------------------------- /nixos/pihole/docker-compose.cloudflared.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | networks: 3 | pihole: 4 | ipam: 5 | config: 6 | - subnet: ${DOCKER_SUBNET} 7 | # More info at https://github.com/pi-hole/docker-pi-hole/ and https://docs.pi-hole.net/ 8 | services: 9 | pihole: 10 | networks: 11 | pihole: 12 | ipv4_address: ${PIHOLE_PRIVATE_ADDRESS} 13 | 14 | cloudflared: 15 | image: crazymax/cloudflared 16 | container_name: cloudflared 17 | hostname: cloudflared 18 | restart: unless-stopped 19 | environment: 20 | TZ: '${TIMEZONE}' 21 | TUNNEL_DNS_UPSTREAM: ${DNS_TUNNEL_DOH} 22 | # ports: 23 | # - "${PIHOLE_DOH_EXPOSE_PORT}:5053/udp" 24 | networks: 25 | pihole: 26 | ipv4_address: ${CLOUDFLARED_PRIVATE_ADDRESS} -------------------------------------------------------------------------------- /nixos/pihole/.example.env: -------------------------------------------------------------------------------- 1 | COMPOSE_PROJECT_NAME=pihole 2 | # Comose file override. Add as necessary 3 | COMPOSE_FILE=docker-compose.yml:docker-compose.cloudflared.yml:docker-compose.dhcpd.yml 4 | 5 | # Specify docker subnet for PiHole to operate on inside the docker network 6 | # This is needed if we uses extra services such as DHCPD helper and Cloudflared DOH 7 | # Because they need to talk each other by IP Address to provide the DNS 8 | DOCKER_SUBNET=172.44.0.0/24 9 | 10 | # Docker PiHole image version 11 | PIHOLE_VERSION=v5.3.4 12 | 13 | # Port overrides 14 | PIHOLE_HTTP_EXPOSE_PORT=80 15 | PIHOLE_HTTPS_EXPOSE_PORT=443 16 | PIHOLE_DNS_EXPOSE_PORT=53 17 | PIHOLE_DOH_EXPOSE_PORT=5053 18 | PIHOLE_DHCPD_EXPOSE_PORT=67 19 | 20 | TIMEZONE=Asia/Jakarta 21 | 22 | WEBPASSWORD=pipihole 23 | SERVER_IP=192.168.100.6 24 | DNS1=172.44.0.3#5053 25 | DNS2=1.1.1.1 26 | 27 | ISP_DNS=1.1.1.1 28 | 29 | # Follow the subnet 30 | PIHOLE_PRIVATE_ADDRESS=172.44.0.2 31 | 32 | # DOH tunnel to use as DNS proxy 33 | DNS_TUNNEL_DOH=https://1.1.1.1/dns-query,https://1.0.0.1/dns-query 34 | 35 | CLOUDFLARED_PRIVATE_ADDRESS=172.44.0.3 -------------------------------------------------------------------------------- /nixos/pihole/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | # More info at https://github.com/pi-hole/docker-pi-hole/ and https://docs.pi-hole.net/ 3 | services: 4 | pihole: 5 | container_name: pihole 6 | hostname: pi.hole 7 | image: pihole/pihole:${PIHOLE_VERSION} 8 | ports: 9 | - "${PIHOLE_DNS_EXPOSE_PORT}:53/tcp" 10 | - "${PIHOLE_DNS_EXPOSE_PORT}:53/udp" 11 | - "${PIHOLE_HTTP_EXPOSE_PORT}:80/tcp" 12 | - "${PIHOLE_HTTPS_EXPOSE_PORT}:443/tcp" 13 | environment: 14 | TZ: '${TIMEZONE}' 15 | WEBPASSWORD: "${WEBPASSWORD}" 16 | VIRTUAL_HOST: pi.hole 17 | PROXY_LOCATION: pi.hole 18 | ServerIP: ${SERVER_IP} 19 | DNS1: ${DNS1} 20 | DNS2: ${DNS2} 21 | DNSMASQ_LISTENING: 22 | # Volumes store your data between container upgrades 23 | dns: 24 | - 127.0.0.1 25 | - ${ISP_DNS} 26 | volumes: 27 | - './etc-pihole/:/etc/pihole/' 28 | - './etc-dnsmasq.d/:/etc/dnsmasq.d/' 29 | # Recommended but not required (DHCP needs NET_ADMIN) 30 | # https://github.com/pi-hole/docker-pi-hole#note-on-capabilities 31 | cap_add: 32 | - NET_ADMIN 33 | restart: unless-stopped 34 | -------------------------------------------------------------------------------- /nixos/pihole/docker-compose.pihole.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | # More info at https://github.com/pi-hole/docker-pi-hole/ and https://docs.pi-hole.net/ 3 | services: 4 | pihole: 5 | container_name: pihole 6 | hostname: pi.hole 7 | image: pihole/pihole:${PIHOLE_VERSION} 8 | ports: 9 | - "${PIHOLE_DNS_EXPOSE_PORT}:53/tcp" 10 | - "${PIHOLE_DNS_EXPOSE_PORT}:53/udp" 11 | - "${PIHOLE_HTTP_EXPOSE_PORT}:80/tcp" 12 | - "${PIHOLE_HTTPS_EXPOSE_PORT}:443/tcp" 13 | environment: 14 | TZ: '${TIMEZONE}' 15 | WEBPASSWORD: "${WEBPASSWORD}" 16 | VIRTUAL_HOST: pi.hole 17 | PROXY_LOCATION: pi.hole 18 | ServerIP: ${SERVER_IP} 19 | DNS1: ${DNS1} 20 | DNS2: ${DNS2} 21 | DNSMASQ_LISTENING: 22 | # Volumes store your data between container upgrades 23 | dns: 24 | - 127.0.0.1 25 | - ${ISP_DNS} 26 | volumes: 27 | - './etc-pihole/:/etc/pihole/' 28 | - './etc-dnsmasq.d/:/etc/dnsmasq.d/' 29 | # Recommended but not required (DHCP needs NET_ADMIN) 30 | # https://github.com/pi-hole/docker-pi-hole#note-on-capabilities 31 | cap_add: 32 | - NET_ADMIN 33 | restart: unless-stopped 34 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; 4 | flake-utils.url = "github:numtide/flake-utils"; 5 | flake-compat = { 6 | url = "github:edolstra/flake-compat"; 7 | flake = false; 8 | }; 9 | devshell.url = "github:numtide/devshell"; 10 | }; 11 | 12 | outputs = { self, nixpkgs, flake-utils, devshell, ... }: 13 | flake-utils.lib.eachDefaultSystem (system: { 14 | apps.devshell = self.outputs.devShell.${system}.flakeApp; 15 | formatter = nixpkgs.legacyPackages.${system}.nixpkgs-fmt; 16 | packages = { 17 | nixosConfigurations = 18 | let 19 | inherit (nixpkgs.lib) nixosSystem; 20 | in 21 | rec { 22 | # to build: nix build github:lucernae/nix-config#nixosConfigurations.raspberry-pi_3.config.system.build.sdImage 23 | raspberry-pi_3 = nixosSystem { 24 | system = "aarch64-linux"; 25 | modules = [ 26 | "${nixpkgs}/nixos/modules/installer/sd-card/sd-image-aarch64-installer.nix" 27 | # replace this with your target configuration 28 | ./configuration.nix 29 | 30 | # extra config for sdImage generator 31 | { 32 | sdImage.compressImage = false; 33 | } 34 | ]; 35 | }; 36 | raspberry-pi_3_default = nixosSystem { 37 | system = "aarch64-linux"; 38 | modules = [ 39 | "${nixpkgs}/nixos/modules/installer/sd-card/sd-image-aarch64-installer.nix" 40 | # replace this with your target configuration 41 | ./configuration.default.nix 42 | 43 | # extra config for sdImage generator 44 | { 45 | sdImage.compressImage = false; 46 | } 47 | ]; 48 | }; 49 | }; 50 | }; 51 | devShell = 52 | let 53 | pkgs = import nixpkgs { 54 | inherit system; 55 | overlays = [ devshell.overlays.default ]; 56 | }; 57 | in 58 | pkgs.devshell.mkShell { 59 | name = "nixos-pi"; 60 | commands = [ 61 | ]; 62 | packages = with pkgs; [ 63 | git 64 | qemu 65 | qemu_kvm 66 | ]; 67 | }; 68 | }); 69 | } 70 | -------------------------------------------------------------------------------- /.github/workflows/nix-build-using-docker.yaml: -------------------------------------------------------------------------------- 1 | name: nix-build-on-demand-docker 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | environmentName: 6 | description: Environment name that stores configuration.nix 7 | required: true 8 | default: default 9 | 10 | jobs: 11 | build-default-image: 12 | name: Build default image 13 | if: ${{ github.event.inputs.environmentName == 'default' }} 14 | runs-on: ubuntu-22.04 15 | steps: 16 | - uses: actions/checkout@v4.1.1 17 | - uses: cachix/install-nix-action@v26 18 | with: 19 | nix_path: nixos=channel:nixos-23.05 20 | extra_nix_config: | 21 | extra-platforms = aarch64-linux 22 | - name: Check nix.conf 23 | run: cat /etc/nix/nix.conf 24 | - name: Register binfmt 25 | run: | 26 | docker run --rm --privileged multiarch/qemu-user-static --reset -p yes 27 | - name: Test binfmt availability 28 | run: | 29 | cat /proc/sys/fs/binfmt_misc/qemu-aarch64 30 | # non flake way to build 31 | # - name: Build SD Image 32 | # run: | 33 | # nix-build '' \ 34 | # -A config.system.build.sdImage \ 35 | # -I nixos-config=./configuration.default.sdImage.nix \ 36 | # --argstr system aarch64-linux \ 37 | # --option sandbox false 38 | - name: Build SD Image 39 | run: | 40 | nix build .#nixosConfigurations.raspberry-pi_3_default.config.system.build.sdImage 41 | - uses: actions/upload-artifact@v4 42 | with: 43 | name: sd-image.img 44 | path: ./result/sd-image/*.img* 45 | 46 | build-custom-image: 47 | name: Build custom image 48 | if: ${{ github.event.inputs.environmentName != 'default' }} 49 | runs-on: ubuntu-22.04 50 | environment: 51 | name: ${{ github.event.inputs.environmentName }} 52 | steps: 53 | - uses: actions/checkout@v4.1.1 54 | - uses: cachix/install-nix-action@v26 55 | with: 56 | nix_path: nixos=channel:nixos-23.05 57 | extra_nix_config: | 58 | extra-platforms = aarch64-linux 59 | - name: Check nix.conf 60 | run: cat /etc/nix/nix.conf 61 | - name: Register binfmt 62 | run: | 63 | docker run --rm --privileged multiarch/qemu-user-static --reset -p yes 64 | - name: Test binfmt availability 65 | run: | 66 | cat /proc/sys/fs/binfmt_misc/qemu-aarch64 67 | - name: Extract configuration from secrets 68 | run: | 69 | cat << EOF >> configuration.nix 70 | ${{ secrets.CONFIGURATION_NIX }} 71 | EOF 72 | # non nix flake way to build 73 | # - name: Build SD Image 74 | # run: | 75 | # nix-build '' \ 76 | # -A config.system.build.sdImage \ 77 | # -I nixos-config=./configuration.custom.sdImage.nix \ 78 | # --argstr system aarch64-linux \ 79 | # --option sandbox false 80 | - name: Build SD Image 81 | run: | 82 | nix build .#nixosConfigurations.raspberry-pi_3_default.config.system.build.sdImage 83 | - uses: actions/upload-artifact@v4 84 | with: 85 | name: sd-image.img 86 | path: ./result/sd-image/*.img* -------------------------------------------------------------------------------- /.github/workflows/nix-build-using-debian.yaml: -------------------------------------------------------------------------------- 1 | # Just a sample workflow if using debian packages for binfmt 2 | name: nix-build-on-demand-debian 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | environmentName: 7 | description: Environment name that stores configuration.nix 8 | required: true 9 | default: default 10 | 11 | jobs: 12 | build-default-image: 13 | name: Build default image 14 | if: ${{ github.event.inputs.environmentName == 'default' }} 15 | runs-on: ubuntu-22.04 16 | steps: 17 | - uses: actions/checkout@v4.1.1 18 | - uses: cachix/install-nix-action@v26 19 | with: 20 | nix_path: nixos=channel:nixos-23.05 21 | extra_nix_config: | 22 | extra-platforms = aarch64-linux 23 | - name: Check nix.conf 24 | run: cat /etc/nix/nix.conf 25 | - uses: myci-actions/add-deb-repo@11 26 | with: 27 | repo: deb http://http.us.debian.org/debian sid main non-free contrib 28 | repo-name: debian-sid 29 | keys: 04EE7237B7D453EC 30 | - run: | 31 | sudo apt -y install qemu-user-static 32 | - name: Test binfmt availability 33 | run: | 34 | cat /proc/sys/fs/binfmt_misc/qemu-aarch64 35 | /usr/bin/qemu-aarch64-static --version 36 | - name: Build SD Image 37 | run: | 38 | nix-build '' \ 39 | -A config.system.build.sdImage \ 40 | -I nixos-config=./configuration.default.sdImage.nix \ 41 | --argstr system aarch64-linux \ 42 | --option sandbox false 43 | - uses: actions/upload-artifact@v4 44 | with: 45 | name: sd-image.img 46 | path: ./result/sd-image/*.img* 47 | 48 | build-custom-image: 49 | name: Build custom image 50 | if: ${{ github.event.inputs.environmentName != 'default' }} 51 | runs-on: ubuntu-22.04 52 | environment: 53 | name: ${{ github.event.inputs.environmentName }} 54 | steps: 55 | - uses: actions/checkout@v4.1.1 56 | - uses: cachix/install-nix-action@v26 57 | with: 58 | nix_path: nixpkgs=channel:nixos-20.09 59 | extra_nix_config: | 60 | extra-platforms = aarch64-linux 61 | - name: Check nix.conf 62 | run: cat /etc/nix/nix.conf 63 | - uses: myci-actions/add-deb-repo@11 64 | with: 65 | repo: deb http://http.us.debian.org/debian sid main non-free contrib 66 | repo-name: debian-sid 67 | keys: 04EE7237B7D453EC 68 | - run: | 69 | sudo apt -y install qemu-user-static 70 | - name: Test binfmt availability 71 | run: | 72 | cat /proc/sys/fs/binfmt_misc/qemu-aarch64 73 | /usr/bin/qemu-aarch64-static --version 74 | - name: Extract configuration from secrets 75 | run: | 76 | cat << EOF >> configuration.custom.sdImage.nix 77 | ${{ secrets.CONFIGURATION_NIX }} 78 | EOF 79 | - name: Build SD Image 80 | run: | 81 | nix-build '' \ 82 | -A config.system.build.sdImage \ 83 | -I nixos-config=./configuration.custom.sdImage.nix \ 84 | --argstr system aarch64-linux \ 85 | --option sandbox false 86 | - uses: actions/upload-artifact@v4 87 | with: 88 | name: sd-image.img 89 | path: ./result/sd-image/*.img* -------------------------------------------------------------------------------- /nixos/pihole/README.md: -------------------------------------------------------------------------------- 1 | # Pi-Hole 2 | 3 | Installing pihole using docker/docker-compose 4 | 5 | [Pi-Hole](https://github.com/pi-hole/pi-hole), the blackhole of ads can be installed relatively easy with docker/docker-compose. 6 | 7 | It also provide a way to declaratively persists the configuration. 8 | 9 | ## Requirements 10 | 11 | - Machine with Docker installed 12 | - Machine with Docker Compose installed 13 | 14 | ## TL;DR 15 | 16 | To run pi-hole, first copy `.example.env` file in this directory and paste it as `.env`. 17 | 18 | Run this command in terminal from within this directory 19 | 20 | ``` 21 | docker-compose up -d 22 | ``` 23 | 24 | To shut it down, go to this directory and execute 25 | 26 | ``` 27 | docker-compose down 28 | ``` 29 | 30 | ## Customization 31 | 32 | The default costumization were intended to run Pi-Hole + CloudflareD DOH + DHCPD Helper inside a RaspberryPi so that it can serve as DHCPD server in the LAN. 33 | 34 | Customization can be done via `.env` file. See comments in the file for more info. Some general customization: 35 | 36 | ### Add/remove docker-compose recipe 37 | 38 | `COMPOSE_FILE` variable contains colon separated list of docker-compose file with file on the right override the file on the left. 39 | 40 | ### Assign different port for Pi-Hole Admin interface 41 | 42 | Specify different port for `PIHOLE_HTTP_EXPOSE_PORT` and `PIHOLE_HTTPS_EXPOSE_PORT`. 43 | 44 | ### Change timezone 45 | 46 | Assign it via `TIMEZONE` variable 47 | 48 | ### Set Pi Hole Server IP 49 | 50 | Useful for analytics and binding in the real RaspberryPi interface. 51 | Set the `SERVER_IP` to your interface IP 52 | 53 | 54 | ### Finding the configuration files 55 | 56 | The configuration files will be generated in `etc-pihole` and `etc-dnsmasq.d` if you want to configure it manually. 57 | 58 | You can also change this into a different location by overriding/modifying `services.pihole.volumes` keys 59 | 60 | ### Change Admin UI password 61 | 62 | Change it via `WEBPASSWORD`. If you already run pihole, then change it from the `etc-pihole/setupVars.conf`. 63 | 64 | ### Set fallback DNS 65 | 66 | You must set your backbone `ISP_DNS` to your ISP's DNS. Or to a DNS IP Address that you believe will work, e.g. 1.1.1.1 or 8.8.8.8. This is used as the DNS that pihole itself will use to fetch it's configuration file. 67 | 68 | If you want to set fallback DNS for the client (if you set your pihole as DHCPD server), then set `DNS1` and `DNS2` in that order of priority. You can also set it the same as `ISP_DNS`, or in my case I set `DNS1` to resolve over DOH (DNS over HTTPS). 69 | 70 | ### Set DNS Tunnel/DoH 71 | 72 | You can specify DoH that CloudflareD will use (if you use CloudflareD as `DNS1`, the default settings). The settings is naemd `DNS_TUNNEL_DOH` 73 | 74 | ### Change private networking 75 | 76 | It maybe possible that the default subnet conflicted with whatever subnet you have in your machine. In this case, change `DOCKER_SUBNET` to specify the subnet that docker will use. You must then change any settings with suffix `_PRIVATE_ADDRESS` to any valid IP Address in that subnet. 77 | 78 | 79 | ## Accessing the Admin UI 80 | 81 | If you already setup your machine to use pi-hole, then you can simply click this link [pi.hole](http://pi.hole) to go to the admin interface (default settings). 82 | 83 | Password is using whatever you set in `WEBPASSWORD`. 84 | 85 | From there you can access it's functionality. 86 | 87 | If you want to reach it by IP address, you may use `http://SERVER_IP:PIHOLE_HTTP_EXPOSE_PORT`. 88 | 89 | 90 | ## Backing up data 91 | 92 | Use the admin interface or just keep `etc-pihole` somewhere safe. -------------------------------------------------------------------------------- /configuration.default.nix: -------------------------------------------------------------------------------- 1 | { config, pkgs, lib, ... }: 2 | { 3 | # NixOS wants to enable GRUB by default 4 | boot.loader.grub.enable = false; 5 | # Enables the generation of /boot/extlinux/extlinux.conf 6 | boot.loader.generic-extlinux-compatible.enable = true; 7 | 8 | # !!! Set to specific linux kernel version 9 | boot.kernelPackages = pkgs.linuxPackages; 10 | 11 | # Disable ZFS on kernel 6 12 | boot.supportedFilesystems = lib.mkForce [ 13 | "vfat" 14 | "xfs" 15 | "cifs" 16 | "ntfs" 17 | ]; 18 | 19 | # !!! Needed for the virtual console to work on the RPi 3, as the default of 16M doesn't seem to be enough. 20 | # If X.org behaves weirdly (I only saw the cursor) then try increasing this to 256M. 21 | # On a Raspberry Pi 4 with 4 GB, you should either disable this parameter or increase to at least 64M if you want the USB ports to work. 22 | boot.kernelParams = [ "cma=256M" ]; 23 | 24 | # File systems configuration for using the installer's partition layout 25 | fileSystems = { 26 | # Prior to 19.09, the boot partition was hosted on the smaller first partition 27 | # Starting with 19.09, the /boot folder is on the main bigger partition. 28 | # The following is to be used only with older images. 29 | /* 30 | "/boot" = { 31 | device = "/dev/disk/by-label/NIXOS_BOOT"; 32 | fsType = "vfat"; 33 | }; 34 | */ 35 | "/" = { 36 | device = "/dev/disk/by-label/NIXOS_SD"; 37 | fsType = "ext4"; 38 | }; 39 | }; 40 | 41 | # !!! Adding a swap file is optional, but strongly recommended! 42 | swapDevices = [{ device = "/swapfile"; size = 1024; }]; 43 | 44 | # Settings above are the bare minimum 45 | # All settings below are customized depending on your needs 46 | 47 | # systemPackages 48 | environment.systemPackages = with pkgs; [ 49 | vim 50 | curl 51 | wget 52 | nano 53 | bind 54 | kubectl 55 | kubernetes-helm 56 | iptables 57 | openvpn 58 | python3 59 | nodejs 60 | docker-compose 61 | ]; 62 | 63 | services.openssh = { 64 | enable = true; 65 | settings.PermitRootLogin = "yes"; 66 | }; 67 | 68 | programs.zsh = { 69 | enable = true; 70 | ohMyZsh = { 71 | enable = true; 72 | theme = "bira"; 73 | }; 74 | }; 75 | 76 | 77 | virtualisation.docker.enable = true; 78 | 79 | networking.firewall.enable = false; 80 | 81 | # WiFi 82 | hardware = { 83 | enableRedistributableFirmware = true; 84 | firmware = [ pkgs.wireless-regdb ]; 85 | }; 86 | 87 | # put your own configuration here, for example ssh keys: 88 | users.defaultUserShell = pkgs.zsh; 89 | users.mutableUsers = true; 90 | users.groups = { 91 | nixos = { 92 | gid = 1000; 93 | name = "nixos"; 94 | }; 95 | }; 96 | users.users = { 97 | nixos = { 98 | uid = 1000; 99 | home = "/home/nixos"; 100 | name = "nixos"; 101 | group = "nixos"; 102 | shell = pkgs.zsh; 103 | extraGroups = [ "wheel" "docker" ]; 104 | }; 105 | }; 106 | users.users.root.openssh.authorizedKeys.keys = [ 107 | # Your ssh key 108 | "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDqlXJv/noNPmZMIfjJguRX3O+Z39xeoKhjoIBEyfeqgKGh9JOv7IDBWlNnd3rHVnVPzB9emiiEoAJpkJUnWNBidL6vPYn13r6Zrt/2WLT6TiUFU026ANdqMjIMEZrmlTsfzFT+OzpBqtByYOGGe19qD3x/29nbszPODVF2giwbZNIMo2x7Ww96U4agb2aSAwo/oQa4jQsnOpYRMyJQqCUhvX8LzvE9vFquLlrSyd8khUsEVV/CytmdKwUUSqmlo/Mn7ge/S12rqMwmLvWFMd08Rg9NHvRCeOjgKB4EI6bVwF8D6tNFnbsGVzTHl7Cosnn75U11CXfQ6+8MPq3cekYr lucernae@lombardia-N43SM" 109 | ]; 110 | system.stateVersion = "23.05"; 111 | } 112 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "devshell": { 4 | "inputs": { 5 | "flake-utils": "flake-utils", 6 | "nixpkgs": "nixpkgs" 7 | }, 8 | "locked": { 9 | "lastModified": 1711099426, 10 | "narHash": "sha256-HzpgM/wc3aqpnHJJ2oDqPBkNsqWbW0WfWUO8lKu8nGk=", 11 | "owner": "numtide", 12 | "repo": "devshell", 13 | "rev": "2d45b54ca4a183f2fdcf4b19c895b64fbf620ee8", 14 | "type": "github" 15 | }, 16 | "original": { 17 | "owner": "numtide", 18 | "repo": "devshell", 19 | "type": "github" 20 | } 21 | }, 22 | "flake-compat": { 23 | "flake": false, 24 | "locked": { 25 | "lastModified": 1696426674, 26 | "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", 27 | "owner": "edolstra", 28 | "repo": "flake-compat", 29 | "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", 30 | "type": "github" 31 | }, 32 | "original": { 33 | "owner": "edolstra", 34 | "repo": "flake-compat", 35 | "type": "github" 36 | } 37 | }, 38 | "flake-utils": { 39 | "inputs": { 40 | "systems": "systems" 41 | }, 42 | "locked": { 43 | "lastModified": 1701680307, 44 | "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", 45 | "owner": "numtide", 46 | "repo": "flake-utils", 47 | "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", 48 | "type": "github" 49 | }, 50 | "original": { 51 | "owner": "numtide", 52 | "repo": "flake-utils", 53 | "type": "github" 54 | } 55 | }, 56 | "flake-utils_2": { 57 | "inputs": { 58 | "systems": "systems_2" 59 | }, 60 | "locked": { 61 | "lastModified": 1710146030, 62 | "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", 63 | "owner": "numtide", 64 | "repo": "flake-utils", 65 | "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", 66 | "type": "github" 67 | }, 68 | "original": { 69 | "owner": "numtide", 70 | "repo": "flake-utils", 71 | "type": "github" 72 | } 73 | }, 74 | "nixpkgs": { 75 | "locked": { 76 | "lastModified": 1704161960, 77 | "narHash": "sha256-QGua89Pmq+FBAro8NriTuoO/wNaUtugt29/qqA8zeeM=", 78 | "owner": "NixOS", 79 | "repo": "nixpkgs", 80 | "rev": "63143ac2c9186be6d9da6035fa22620018c85932", 81 | "type": "github" 82 | }, 83 | "original": { 84 | "owner": "NixOS", 85 | "ref": "nixpkgs-unstable", 86 | "repo": "nixpkgs", 87 | "type": "github" 88 | } 89 | }, 90 | "nixpkgs_2": { 91 | "locked": { 92 | "lastModified": 1711715736, 93 | "narHash": "sha256-9slQ609YqT9bT/MNX9+5k5jltL9zgpn36DpFB7TkttM=", 94 | "owner": "NixOS", 95 | "repo": "nixpkgs", 96 | "rev": "807c549feabce7eddbf259dbdcec9e0600a0660d", 97 | "type": "github" 98 | }, 99 | "original": { 100 | "owner": "NixOS", 101 | "ref": "nixpkgs-unstable", 102 | "repo": "nixpkgs", 103 | "type": "github" 104 | } 105 | }, 106 | "root": { 107 | "inputs": { 108 | "devshell": "devshell", 109 | "flake-compat": "flake-compat", 110 | "flake-utils": "flake-utils_2", 111 | "nixpkgs": "nixpkgs_2" 112 | } 113 | }, 114 | "systems": { 115 | "locked": { 116 | "lastModified": 1681028828, 117 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 118 | "owner": "nix-systems", 119 | "repo": "default", 120 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 121 | "type": "github" 122 | }, 123 | "original": { 124 | "owner": "nix-systems", 125 | "repo": "default", 126 | "type": "github" 127 | } 128 | }, 129 | "systems_2": { 130 | "locked": { 131 | "lastModified": 1681028828, 132 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 133 | "owner": "nix-systems", 134 | "repo": "default", 135 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 136 | "type": "github" 137 | }, 138 | "original": { 139 | "owner": "nix-systems", 140 | "repo": "default", 141 | "type": "github" 142 | } 143 | } 144 | }, 145 | "root": "root", 146 | "version": 7 147 | } 148 | -------------------------------------------------------------------------------- /configuration.nix: -------------------------------------------------------------------------------- 1 | { config, pkgs, lib, ... }: 2 | { 3 | # NixOS wants to enable GRUB by default 4 | boot.loader.grub.enable = false; 5 | # Enables the generation of /boot/extlinux/extlinux.conf 6 | boot.loader.generic-extlinux-compatible.enable = true; 7 | 8 | # !!! Set to specific linux kernel version 9 | boot.kernelPackages = pkgs.linuxPackages; 10 | 11 | # Disable ZFS on kernel 6 12 | boot.supportedFilesystems = lib.mkForce [ 13 | "vfat" 14 | "xfs" 15 | "cifs" 16 | "ntfs" 17 | ]; 18 | 19 | # !!! Needed for the virtual console to work on the RPi 3, as the default of 16M doesn't seem to be enough. 20 | # If X.org behaves weirdly (I only saw the cursor) then try increasing this to 256M. 21 | # On a Raspberry Pi 4 with 4 GB, you should either disable this parameter or increase to at least 64M if you want the USB ports to work. 22 | boot.kernelParams = [ "cma=256M" ]; 23 | 24 | # File systems configuration for using the installer's partition layout 25 | fileSystems = { 26 | # Prior to 19.09, the boot partition was hosted on the smaller first partition 27 | # Starting with 19.09, the /boot folder is on the main bigger partition. 28 | # The following is to be used only with older images. 29 | /* 30 | "/boot" = { 31 | device = "/dev/disk/by-label/NIXOS_BOOT"; 32 | fsType = "vfat"; 33 | }; 34 | */ 35 | "/" = { 36 | device = "/dev/disk/by-label/NIXOS_SD"; 37 | fsType = "ext4"; 38 | }; 39 | }; 40 | 41 | # !!! Adding a swap file is optional, but strongly recommended! 42 | swapDevices = [{ device = "/swapfile"; size = 1024; }]; 43 | 44 | # systemPackages 45 | environment.systemPackages = with pkgs; [ 46 | vim 47 | curl 48 | wget 49 | nano 50 | bind 51 | kubectl 52 | kubernetes-helm 53 | iptables 54 | openvpn 55 | python3 56 | nodejs 57 | docker-compose 58 | ]; 59 | 60 | services.openssh = { 61 | enable = true; 62 | settings.PermitRootLogin = "yes"; 63 | }; 64 | 65 | # Some sample service. 66 | # Use dnsmasq as internal LAN DNS resolver. 67 | services.dnsmasq = { 68 | enable = false; 69 | settings.servers = [ "8.8.8.8" "8.8.4.4" "1.1.1.1" ]; 70 | settings.extraConfig = '' 71 | address=/fenrir.test/192.168.100.6 72 | address=/recalune.test/192.168.100.7 73 | address=/eth.nixpi.test/192.168.100.3 74 | address=/wlan.nixpi.test/192.168.100.4 75 | ''; 76 | }; 77 | 78 | # services.openvpn = { 79 | # # You can set openvpn connection 80 | # servers = { 81 | # privateVPN = { 82 | # config = "config /home/nixos/vpn/privatvpn.conf"; 83 | # }; 84 | # }; 85 | # }; 86 | 87 | programs.zsh = { 88 | enable = true; 89 | ohMyZsh = { 90 | enable = true; 91 | theme = "bira"; 92 | }; 93 | }; 94 | 95 | 96 | virtualisation.docker.enable = true; 97 | 98 | networking.firewall.enable = false; 99 | 100 | 101 | # WiFi 102 | hardware = { 103 | enableRedistributableFirmware = true; 104 | firmware = [ pkgs.wireless-regdb ]; 105 | }; 106 | # Networking 107 | networking = { 108 | # useDHCP = true; 109 | interfaces.wlan0 = { 110 | useDHCP = false; 111 | ipv4.addresses = [{ 112 | # I used static IP over WLAN because I want to use it as local DNS resolver 113 | address = "192.168.1.4"; 114 | prefixLength = 24; 115 | }]; 116 | }; 117 | interfaces.eth0 = { 118 | useDHCP = true; 119 | # I used DHCP because sometimes I disconnect the LAN cable 120 | #ipv4.addresses = [{ 121 | # address = "192.168.100.3"; 122 | # prefixLength = 24; 123 | #}]; 124 | }; 125 | 126 | # Enabling WIFI 127 | wireless.enable = true; 128 | wireless.interfaces = [ "wlan0" ]; 129 | # If you want to connect also via WIFI to your router 130 | # wireless.networks."SATRIA".psk = "wifipassword"; 131 | # You can set default nameservers 132 | # nameservers = [ "192.168.100.3" "192.168.100.4" "192.168.100.1" ]; 133 | # You can set default gateway 134 | # defaultGateway = { 135 | # address = "192.168.1.1"; 136 | # interface = "eth0"; 137 | # }; 138 | }; 139 | 140 | # forwarding 141 | boot.kernel.sysctl = { 142 | "net.ipv4.conf.all.forwarding" = true; 143 | "net.ipv6.conf.all.forwarding" = true; 144 | "net.ipv4.tcp_ecn" = true; 145 | }; 146 | 147 | # put your own configuration here, for example ssh keys: 148 | users.defaultUserShell = pkgs.zsh; 149 | users.mutableUsers = true; 150 | users.groups = { 151 | nixos = { 152 | gid = 1000; 153 | name = "nixos"; 154 | }; 155 | }; 156 | users.users = { 157 | nixos = { 158 | uid = 1000; 159 | home = "/home/nixos"; 160 | name = "nixos"; 161 | group = "nixos"; 162 | shell = pkgs.zsh; 163 | extraGroups = [ "wheel" "docker" ]; 164 | }; 165 | }; 166 | users.users.root.openssh.authorizedKeys.keys = [ 167 | # This is my public key 168 | "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDqlXJv/noNPmZMIfjJguRX3O+Z39xeoKhjoIBEyfeqgKGh9JOv7IDBWlNnd3rHVnVPzB9emiiEoAJpkJUnWNBidL6vPYn13r6Zrt/2WLT6TiUFU026ANdqMjIMEZrmlTsfzFT+OzpBqtByYOGGe19qD3x/29nbszPODVF2giwbZNIMo2x7Ww96U4agb2aSAwo/oQa4jQsnOpYRMyJQqCUhvX8LzvE9vFquLlrSyd8khUsEVV/CytmdKwUUSqmlo/Mn7ge/S12rqMwmLvWFMd08Rg9NHvRCeOjgKB4EI6bVwF8D6tNFnbsGVzTHl7Cosnn75U11CXfQ6+8MPq3cekYr lucernae@lombardia-N43SM" 169 | ]; 170 | system.stateVersion = "23.05"; 171 | } 172 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NixOS on Raspberry Pi 2 | 3 | My personal notes on how to setup NixOS on Raspberry Pi 4 | 5 | Model: 6 | - Pi 3B+ 7 | 8 | # Using prebuilt image available on Hydra 9 | 10 | The latest image is on Hydra (example. You might need to change NixOS version in the following URL): 11 | 12 | [sd-image](https://hydra.nixos.org/job/nixos/release-20.09/nixos.sd_image.aarch64-linux/latest/download-by-type/file/sd-image) 13 | 14 | If the image have extension .img.zst , then you need to uncompress it. 15 | 16 | ```bash 17 | unzstd sd-image.img.zst 18 | ``` 19 | 20 | Flash the image to your sd card using dd. Follow the instruction in NixOS [wiki](https://nixos.wiki/wiki/NixOS_on_ARM#Installation_steps) that links to the raspberry pi original docs. 21 | 22 | With this setup, you have a bare minimum image that you can access via HDMI and keyboard. 23 | 24 | Further configuration are done via `/etc/nixos/configuration.nix` (the image is a NixOS). 25 | Create the file and fill in your configuration. 26 | Here's some example from mine: [configuration.nix](configuration.nix) 27 | 28 | Build the configuration 29 | 30 | ``` 31 | nixos-rebuild test -p test 32 | ``` 33 | 34 | This will build the configuration but not set it as the default boot. You can try if everything works okay and then reboot. From the boot menu, choose this profile to test if everything works after reboot. If you do nothing in the boot menu, it will choose your last default profile instead of this `test` profile. 35 | It is also possible to create different nixos-config file and build it accordingly to test several config: 36 | 37 | ``` 38 | nixos-rebuild test -p test-1 -I nixos-config=./test.nix 39 | ``` 40 | 41 | # Building using Github Action 42 | 43 | We can leverage Github Action to build our image. We can reuse existing action to setup qemu-user-static and then build and deploy the image as workflow artifact. You can then download the artifact from Github, which is the zipped sd-image.img file. 44 | 45 | I already setup a workflow manual dispatch Github Action in this repo, so to build your own customized NixOS raspi image, follow this steps. 46 | 47 | 1. Fork the repo so you can build your own custom image 48 | 2. Create your build/deployment environment. 49 | 50 | From your repo settings page, click the Environments menu. Click New environment. Give it a name other than `default`. Define environment secrets called `CONFIGURATION_NIX`. 51 | The content should be your custom `configuration.nix` file. 52 | This will be imported by the `configuration.sdImage.nix`. 53 | 54 | 3. Run your workflow 55 | 56 | In the Actions page, select `nix-build-on-demand-docker` action and then click `Run workflow`. You will be given an option to specify the environment name. Fill in the name of the environment you set up in step 2. Click Run workflow. If you use `default` environment name, it will build [configuration.default.sdImage.nix](configuration.default.sdImage.nix) as the recipe. 57 | 58 | 4. Wait for it to finish 59 | 60 | 5. Retrieve the artifact 61 | 62 | When the build finish, in your action job page, there will be Artifacts panel with artifacts named `sd-image.img`. Click on it and it will download a zipped file. Extract the zipfile and it will contain the image, as `.img` or `.img.zstd` depending on your config you provided. 63 | 64 | # Building on x86/64 machine 65 | 66 | The reason you may want to build your image yourself is because you want to store the initial config as an image. 67 | For example, it may include your own initial service like SSH, or network configuration (static IP, WIFI password, etc). 68 | 69 | You need NixOS or just Nix package manager 70 | 71 | You need QEMU ARM if you only have Nix. 72 | 73 | ## Example in Ubuntu 74 | 75 | For example to use it in Ubuntu: 76 | 77 | *notes* for some reason, sd image build failed to build if we are using latest Linux kernel (5.4 currently). So, we need to use latest QEMU user-static that is available on debian sid (currently) or QEMU user-static version 5.x.x. Adding the repository is beyond the scope of this README and please do so if you understand that it is coming from and unstable apt repo. 78 | 79 | ```bash 80 | sudo apt -y install qemu-user-static 81 | ``` 82 | 83 | Since we are going to run aarch64 binaries inside our x86_64 box, we need qemu-user-static to run aarch64 executable transparently. 84 | Check that `binfmt_misc` now support this: 85 | 86 | ```bash 87 | ls -l /proc/sys/fs/binfmt_misc | grep aarch64 88 | ``` 89 | 90 | If it returns something (`qemu-aarch64`), you are on the right track. 91 | 92 | Add the following line to `/etc/nix/nix.conf`: `extra-platforms = aarch64-linux` . If the file doesn't exist, create it. 93 | 94 | Create a base nix file for SD Card image build. Typically this contains some config for basic setup that you want to make it work right after flashing the image. 95 | For example, public SSH keys, or static IP address settings, or WI-FI password, list of system packages and services, etc. 96 | 97 | The nix file must import the SD Image packages 98 | 99 | ```nix 100 | { config, pkgs, lib, ... }: 101 | { 102 | 103 | imports = [ 104 | 105 | ]; 106 | 107 | # Do not compress the image as we want to use it straight away 108 | sdImage.compressImage = false; 109 | 110 | # The rest of user config goes here 111 | } 112 | ``` 113 | 114 | See example in: [configuration.sdImage.nix](configuration.sdImage.nix) 115 | 116 | Then build the image: 117 | 118 | ``` 119 | nix-build '' -A config.system.build.sdImage -I nixos-config=./configuration.sdImage.nix \ 120 | --argstr system aarch64-linux \ 121 | --option sandbox false 122 | ``` 123 | 124 | When the image finally built (normally you don't want to compress it), you can flash it to SD card like in the above instructions. 125 | Boot your pi and access it (via Keyboard + HDMI, or over SSH), then you need to `fix` your `/etc/nixos/configuration.nix`. 126 | The nixos config that you made for building the image is for installation image, meanwhile you may have different nixos config after that. 127 | Typical configuration includes deleting the import lines for sd-image (So you don't rebuild image again), specifying basic fs mount (or additionally swap). 128 | 129 | ## Example in NixOS 130 | 131 | If you have NixOS, adding binfmt support is super easy. 132 | 133 | Just add the binfmt support in your `/etc/nixos/configuration.nix` 134 | 135 | ``` 136 | # add this line inside the nix function 137 | boot.binfmt.emulatedSystems = [ "aarch64-linux" ]; 138 | ``` 139 | 140 | Then, `nixos-rebuild switch` your configuration and your NixOS is ready to be used for cross-compilation. 141 | 142 | The rest of the steps are the same with the Ubuntu example above after installing `qemu-user-static` 143 | 144 | # Building on ARM machine with Linux 145 | 146 | Same as above, but you don't need to install QEMU. You just need Nix or NixOS. 147 | 148 | The build command: 149 | 150 | ``` 151 | # notice that we don't need to specify --argstr system aarch64-linux 152 | nix-build '' -A config.system.build.sdImage -I nixos-config=./configuration.sdImage.nix \ 153 | --option sandbox false 154 | ``` 155 | 156 | # Building using Nix Flake 157 | 158 | You must be on a NixOS machine or Nix on Linux. The architecture won't matter. 159 | 160 | Following the previous guide on Building in x86_64 or ARM machine with Linux, the command is replaced 161 | with Nix Flake command. 162 | 163 | ```shell 164 | nix build .#nixosConfigurations.raspberry-pi_3.config.system.build.sdImage 165 | ``` 166 | 167 | Note, that since you can execute nix build on a remote flake, if your `configuration.nix` is already 168 | stored in your repo, then you can build locally against remote flake (no need to git clone). 169 | 170 | ```shell 171 | # example using this repo as the remote flake address 172 | nix build github:lucernae/nixos-pi#nixosConfigurations.raspberry-pi_3.config.system.build.sdImage 173 | ``` 174 | 175 | # Building using Docker 176 | 177 | Theoritically we can also build cross-platform using Docker container. 178 | Normally this is used to build cross platform docker images, but we can also use it to build cross-platform in the host. 179 | 180 | You need docker or podman installed as a prerequisite. 181 | 182 | First we need to register the binfmt from a docker image. We use this repository: [https://hub.docker.com/r/multiarch/qemu-user-static](https://hub.docker.com/r/multiarch/qemu-user-static). 183 | 184 | ```bash 185 | docker run --rm --privileged multiarch/qemu-user-static --reset -p yes 186 | ``` 187 | 188 | The trick works like this. Normally the image above is used to register qemu-user-static interpreter inside the container. Since `/proc/sys/fs/binfmt_misc` are the same in the host and container, if the image were run using `--privileged`, then the binfmt in the host are also registered with the binaries inside the docker image. So basically the docker image serves as a convenient packaging library for qemu-user-static. 189 | 190 | According to the documentation, the `-p yes` flag tells the image to register the binfmt and persists it even if the container exits. So the interpreter are also available in the host kernel. However you can't check the interpreter version directly in the host, since the binaries don't exists in the host (but in the image above). To check the version, the author uses the convention like this: 191 | 192 | ```bash 193 | # supply the platform as image tag, e.g. aarch64 194 | docker run --rm --privileged multiarch/qemu-user-static:aarch64 /usr/bin/qemu-aarch64-static --version 195 | ``` 196 | 197 | Now your kernel can execute aarch64 binaries and you can cross-compile. There rest of the steps are the same. 198 | 199 | # Reference 200 | 201 | - [NixOS on ARM installation notes](https://nixos.wiki/wiki/NixOS_on_ARM#Installation) 202 | - [NixOS Pi 3 installation notes](https://nixos.wiki/wiki/NixOS_on_ARM/Raspberry_Pi_3#Board-specific_installation_notes) --------------------------------------------------------------------------------