Skip to content

Repository files navigation

Reusable NixOS Server Configuration

Main repo: codeberg.org/giggio/nixos_serverbase

This project provides a modular and reusable NixOS configuration, primarily targeted at building server environments for Raspberry Pi 4 and Gmktec G3 Plus, plus QEMU for testing.

It is structured as a Nix Flake that can be consumed by other projects to inherit a base server configuration while allowing specific machine customizations.

It can also run by itself for creating a default, base configuration, just to see how it works.

This is my personal base environment and is very opinionated, it won't necessarily work for you. Use it as you will, or don't use it at all and just use it for some ideas that could be useful.

Warning: Be careful with the ISO installer, it will overwrite the target system without prompting.

Architecture

  • modules/serverbase/ (directory): The core reusable module. It includes standard packages, Home Manager integration, encryption setup (SOPS), and general system settings.
  • modules/lib.nix: Provides helper functions to build system artifacts (Pi4 images, ISOs, QEMU VMs) and development shells.
  • configuration.nix: A specific machine configuration (example: pi4) that imports the serverbase and applies host-specific settings.
  • docs/: Board-specific documentation, for hardware that needed more explanation than a comment can carry. See Documentation.

Documentation

Most of what this repository does is explained by comments next to the code. Two things did not fit there, both about the Orange Pi 4 Pro — a board with no mainline Linux or U-Boot support, where the boot chain is reproduced from Allwinner's vendor BSP:

  • docs/opi4pro/DISASTER-RECOVERY.md — the runbook for when the board does not boot. It assumes you remember nothing and walks from "the board is dead" to "the board boots again", cheapest repair first. Read it before you need it, at least once.
  • docs/opi4pro/BRINGUP-REPORT.md — how the port was done and why it is shaped the way it is: the boot chain, the unattended installer, USB-2 host mode, and every failure met along the way. Read this when you want to change something and need to know what a given line is load-bearing for.

Usage as a flake (library)

You can import this project in your own flake.nix to build your custom servers.

1. In your flake.nix

Use nixosModules.default to get the base configuration (includes serverbase, sops, and home-manager).

{
  description = "NixOS configuration";
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-26.05";
    serverbase = {
      url = "git+https://codeberg.org/giggio/nixos_serverbase.git?ref=main";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs =
    {
      nixpkgs,
      serverbase,
      flake-utils,
      ...
    }:
    let
      machines = [
        rec {
          name = "pi4";
          defaultArch = "aarch64";
          hardwareModule = serverbase.nixosModules.hardware.pi4;
          modules = [ ./machines/${name}/configuration.nix ];
          supportsIso = false;
          supportsImg = true;
        }
        rec {
          name = "gmktec1";
          defaultArch = "x86_64";
          hardwareModule = serverbase.nixosModules.hardware.gmktec;
          modules = [ ./machines/${name}/configuration.nix ];
          supportsIso = true;
          supportsImg = false;
          vmMemorySize = 8;
          vmDiskSize = 48;
        }
      ];
      nixosConfigurations = serverbase.nixosModules.lib.mkNixosConfigurations machines;
    in
    {
      inherit nixosConfigurations;
    }
    // flake-utils.lib.eachDefaultSystem (
      system:
      let
        pkgs = import nixpkgs { inherit system; };
      in
      {
        formatter = pkgs.nixfmt-tree;
        packages = {
          list_machines = serverbase.nixosModules.lib.list_machines { inherit pkgs machines; };
        }
        // serverbase.nixosModules.lib.mkInstallerPackages {
          inherit nixosConfigurations machines;
        };
        devShells.default = serverbase.nixosModules.lib.mkDevShell {
          inherit pkgs;
          inherit system;
        };
      }
    );
}

Available modules in nixosModules:

  • default: Base list of modules (recommended).
  • lib: Useful helper functions.
  • hardware: Configuration for known hardware, both physical and virtual.

3. Building Artifacts for deployment

You can build a Raspberry Pi 4 image that can be used to create an installation SD card, or an ISO that can be used to install on Gmktec G3 Plus. You can also create QEMU VMs.

mkInstallerPackages produces these per machine:

Package What it is
<machine>_img SD image. For most boards the whole system runs from the card; for machines with imgIsInstaller = true it is an unattended installer that wipes the target disk and installs onto it.
<machine>boot_img Boot-only SD image, for imgIsInstaller machines only. Reproduces just the boot chain so a card can be replaced without reinstalling. Never touches the system disk.
<machine>_iso Installer ISO, for machines with supportsIso = true.
<machine><arch>vm QEMU run script.

Each also has a dev variant (<machine>dev_img, <machine>devboot_img, …).

See the above example with mkInstallerPackages.

4. Using the Makefile

If you copy this project's Makefile to your project, you can use it to easily build the artifacts. Run make help for more information.

It will take the information you add to the machines (see example above) to generate custom targets, so you could run out/nix/vm/run-gmktec1dev-vm to build the QEMU run script, or out/nix/img/pi4.img.zst to build the Raspberry Pi 4 image, or out/nix/iso/gmktec1.iso to build the Gmktec G3 Plus ISO. You could also view the whole file system that will be generated by running make out/nix/system/pi4.

Developing with QEMU

Note: set the VMS_DIR environment variable to the directory where you want to store the VMs. By default it will use $HOME/vms, set by the flake dev shell.

All examples are with gmktec1dev, but you should replace with the name of your machine.

  1. Install QEMU or use the flake dev shell.
  2. Set up your secrets (see Secrets);
  3. Start a new VM with make start_new_gmktec1dev, this will connect to the serial port of the machine.
  4. After the machine boots, you can also connect to it via SSH on localhost, port 2222.
  5. After the machine exits you can start the same machine again with make start_new_gmktec1dev.

The secrets will be automatically added to a separate disk in the VM during the build process if using the provided scripts.

If you have problems, inspect the virtual machine script after you import it, make sure it matches your hardware.

You can also use make create_gmktec1, which will create an empty VM but connect an .iso to it, and when it boots it will install the OS. This is useful to test the .iso installer. This is much slower as the ISO creation and then the copy to the QEMU disk is slow. There is no similar way to test the .img installer.

Building the Default Configs

This is not very helpful (as the servers will not do anything useful), but will help you get a sense of what you can do with this library.

You can use this repository directly to build the default machine (e.g., for testing or as a starting point):

Deploying to Raspberry Pi 4

  1. Clone this repo and set up your secrets (see Secrets).

  2. Build it with make out/nix/img/pi4.img.zst. Or build with nix:

    nix build .#pi4_img
  3. Burn it into the SD card using the Raspberry Pi Imager. For the operating system, select the last option, "Use custom" and select the image.

  4. Load the sd card into the Raspberry Pi 4.

  5. Copy the secret file server.agekey to the root of a USB flash drive and connect the device to the Pi 4.

You can also burn by running (replace /dev/sdX by the appropriate drive):

zstdcat out/nix/img/pi4.img.zst | sudo dd of=/dev/sdX bs=4M status=progress conv=fsync

Deploying to the Orange Pi 4 Pro

It is very similar to the Raspberry Pi 4 above, just change the out file to:

zstdcat out/nix/img/opi4pro.img.zst | sudo dd of=/dev/sdX bs=4M status=progress conv=fsync

This board is different from the others in two ways worth knowing before you flash anything:

  • The .img is an unattended installer, not a full system on a card. It boots the board, wipes the NVMe SSD and installs onto it. The board then runs from the SSD, and the SD card stays in forever holding only the boot chain — the SoC's boot ROM can only read the first-stage loader from SD raw sectors.
  • The final system's closure must be in the binary cache before the installer runs, or the board will try to build the vendor kernel and U-Boot itself. make cache_opi4pro pushes it.

To replace a worn-out or undersized SD card without reinstalling, build the boot-only image instead. It carries just the bootloader region and the four boot files, is about 304 MiB (~51 MiB compressed), and never touches the SSD:

make out/nix/img/opi4proboot.img.zst          # or: nix build .#opi4proboot_img
zstdcat out/nix/img/opi4proboot.img.zst | sudo dd of=/dev/sdX bs=4M status=progress conv=fsync

The card is tied to one system generation (boot.scr bakes an absolute init=/nix/store/... path), so build it from the revision the board is actually running and verify before flashing — see docs/opi4pro/DISASTER-RECOVERY.md.

Deploying to Gmktec G3 Plus

  1. Clone this repo and set up your secrets (see Secrets).

  2. Build it with make out/nix/iso/gmktec1.iso. Or build with nix:

    nix build .#gmktec1_iso
  3. Burn it into the flash drive using your preferred tool. The easiest is to use dd (change sda for your device):

    sudo dd if=out/nix/iso/gmktec1.iso of=/dev/sda bs=4M status=progress
  4. Load the flash drive into the Gmktec G3 Plus.

  5. Copy the secret file server.agekey to the root of a USB flash drive and connect the device to the Gmktec G3 Plus.

Local Development Tools

Entering the Dev Shell

Provides all necessary tools like SOPS, build utilities, etc.

nix develop
# or if you use direnv:
direnv allow

Running Tests

Run the integrated NixOS verification tests (boots a VM and runs checks):

nix flake check

Tests need more work and probably not working.

Secrets

Server key file

The sops secrets file should be at $HOME/.config/nixos-secrets/server.agekey. Generate the key file with:

nix shell nixpkgs#age -c age-keygen -o $HOME/.config/nixos-secrets/server.agekey

Update the .sops.yaml with the key:

  1. View public key: grep public ~/.config/nixos-secrets/server.agekey
  2. Update .sops.yaml (automated helper):
key=$(grep public ~/.config/nixos-secrets/server.agekey | sed 's/.*: //')
sed -i -E "s/(.*pi4 )(.*)( #)/\$key\3/" .sops.yaml

Gpg key

You need a gpg key to encrypt the secrets. You can find your fingerprint with:

gpg --with-colons --fingerprint | awk -F: '$1 == "fpr" {print $10; exit}'

If you have more than one key, this will print multiple lines. Choose the key that you need, or you can use all of them. Add the key to the .sops.yaml file, replacing the one that is there under giggio.

Editing the secrets file

The secrets file is at ./modules/serverbase/secrets/shared.yaml. You can edit it with:

sops modules/serverbase/secrets/shared.yaml # if using the flake default shell with `nix develop` or `direnv`
# or
nix run nixpkgs#sops modules/serverbase/secrets/shared.yaml # if not using the flake default shell

You will need to use one of the keys listed in the .sops.yaml file. If you don't have it, remove the file and create a new one.

You can find the file layout by looking at ./modules/serverbase/secrets.nix.

Contributing

Questions, comments, bug reports, and pull requests are all welcome. Submit them at the project on Codeberg.

Bug reports that include steps-to-reproduce (including code) are the best. Even better, make them in the form of pull requests. Pull requests on Github will probably be ignored, so avoid them.

Author

Giovanni Bassi

License

Licensed under the MIT license.

About

An opinionated, modular and reusable NixOS configuration

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Used by

Contributors

Languages