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.
- 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.
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.
You can import this project in your own flake.nix to build your custom servers.
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.
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.
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.
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.
- Install QEMU or use the flake dev shell.
- Set up your secrets (see Secrets);
- Start a new VM with
make start_new_gmktec1dev, this will connect to the serial port of the machine. - After the machine boots, you can also connect to it via SSH on localhost, port 2222.
- 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.
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):
-
Clone this repo and set up your secrets (see Secrets).
-
Build it with
make out/nix/img/pi4.img.zst. Or build with nix:nix build .#pi4_img -
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.
-
Load the sd card into the Raspberry Pi 4.
-
Copy the secret file
server.agekeyto 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=fsyncIt 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=fsyncThis board is different from the others in two ways worth knowing before you flash anything:
- The
.imgis 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_opi4propushes 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=fsyncThe 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.
-
Clone this repo and set up your secrets (see Secrets).
-
Build it with
make out/nix/iso/gmktec1.iso. Or build with nix:nix build .#gmktec1_iso -
Burn it into the flash drive using your preferred tool. The easiest is to use
dd(changesdafor your device):sudo dd if=out/nix/iso/gmktec1.iso of=/dev/sda bs=4M status=progress
-
Load the flash drive into the Gmktec G3 Plus.
-
Copy the secret file
server.agekeyto the root of a USB flash drive and connect the device to the Gmktec G3 Plus.
Provides all necessary tools like SOPS, build utilities, etc.
nix develop
# or if you use direnv:
direnv allowRun the integrated NixOS verification tests (boots a VM and runs checks):
nix flake checkTests need more work and probably not working.
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.agekeyUpdate the .sops.yaml with the key:
- View public key:
grep public ~/.config/nixos-secrets/server.agekey - Update
.sops.yaml(automated helper):
key=$(grep public ~/.config/nixos-secrets/server.agekey | sed 's/.*: //')
sed -i -E "s/(.*pi4 )(.*)( #)/\$key\3/" .sops.yamlYou 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.
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 shellYou 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.
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.
Licensed under the MIT license.