[PATCH 3/8] nixos: build the per-node disk image through the flake output
Daniel Gomez <[email protected]> Thu, 23 Apr 2026 00:48:41 +0200
| Newsgroups | dev.linux.lists.kdevops |
|---|---|
| Message-ID | <20260423-kdevops-series-b-nixos-qemu-v1-3-209154ae54f8@samsung.com> |
From: Daniel Gomez <[email protected]> Let nix build resolve the nixos-qemu libvirt template's packages.<system>.image output instead of stitching qcow2s together through a jinja-rendered disk-image.nix plus a hand-rolled make-image.nix wrapper. The template already encodes everything the old wrapper patched on top of the closure (boot loader, root filesystem, kernel modules, qemu-guest profile), so keeping both would mean keeping kdevops' mkForce overrides in sync with whatever nixos-qemu decides. Leaning on the flake output makes the library authoritative and collapses the kdevops side to building and copying. Generated-by: Claude AI Signed-off-by: Daniel Gomez <[email protected]> (cherry picked from commit ea76a5645a6a2ca0cbcfca56ef348021bbf94d61) --- playbooks/nixos.yml | 156 +++++-------------------------------------- scripts/nixos_build_image.sh | 38 +++++++++++ 2 files changed, 54 insertions(+), 140 deletions(-) diff --git a/playbooks/nixos.yml b/playbooks/nixos.yml index 007c9033..7db0ab63 100644 --- a/playbooks/nixos.yml +++ b/playbooks/nixos.yml @@ -153,149 +153,25 @@ - "{{ playbook_dir }}/../extra_vars.yaml" tags: build-vms tasks: - - name: Create disk image configuration - ansible.builtin.copy: - content: | - { config, lib, pkgs, ... }: - - { - imports = [ - ./configuration.nix - ]; - - # Ensure proper boot configuration for disk image - boot.loader.grub.device = lib.mkForce "/dev/vda"; - boot.loader.grub.enable = lib.mkForce true; - - fileSystems."/" = lib.mkForce { - device = "/dev/disk/by-label/nixos"; - fsType = "ext4"; - autoResize = true; - }; - } - dest: "{{ nixos_generation_dir }}/disk-image.nix" - - - name: Check if NixOS disk image already exists - ansible.builtin.stat: - path: "{{ nixos_storage_dir }}/nixos-image-result" - register: disk_image_exists - - - name: Build NixOS disk image - ansible.builtin.shell: | - # Source nix profile and set PATH - export PATH="/nix/var/nix/profiles/default/bin:/usr/local/bin:/usr/bin:/bin:$PATH" - if [ -f /nix/var/nix/profiles/default/etc/profile.d/nix.sh ]; then - . /nix/var/nix/profiles/default/etc/profile.d/nix.sh - fi - - # Configure Nix to use local mirror if available - {% if nixos_use_local_mirror is defined and nixos_use_local_mirror %} - # Dynamically detect mirror URL if not provided - {% if nixos_mirror_url is not defined or nixos_mirror_url == "" %} - DETECTED_MIRROR=$(bash {{ playbook_dir }}/../scripts/check_nix_mirror.sh NIX_CACHE_MIRROR_URL) - if [ -n "$DETECTED_MIRROR" ]; then - export NIX_CONFIG="substituters = $DETECTED_MIRROR https://cache.nixos.org" - echo "Using detected local Nix cache mirror: $DETECTED_MIRROR" - fi - {% else %} - export NIX_CONFIG="substituters = {{ nixos_mirror_url }} https://cache.nixos.org" - echo "Using configured local Nix cache mirror: {{ nixos_mirror_url }}" - {% endif %} - {% endif %} - - cd {{ nixos_generation_dir }} - - # Build a QCOW2 disk image with NixOS installed - echo "Building NixOS disk image (this may take a while)..." - - # Create a wrapper expression for make-disk-image.nix - cat > make-image.nix <<'EOF' - let - pkgs = import <nixpkgs> {}; - lib = pkgs.lib; - - # Build a complete NixOS system configuration - nixosSystem = import "${pkgs.path}/nixos" { - configuration = { - imports = [ - ./configuration.nix - ./disk-image.nix - ]; - - # Ensure we have a bootable system - boot.loader.grub.enable = lib.mkForce true; - boot.loader.grub.device = lib.mkForce "/dev/vda"; - boot.loader.grub.configurationLimit = 1; - - # Critical: ensure the system can boot - boot.kernelModules = [ "virtio_pci" "virtio_blk" "virtio_net" ]; - boot.initrd.availableKernelModules = [ "virtio_pci" "virtio_blk" "virtio_net" ]; - - # Ensure networking works - networking.useDHCP = lib.mkDefault true; - - # Make sure we have a working system - system.stateVersion = "24.05"; - - # Ensure SSH starts - systemd.services.sshd.wantedBy = [ "multi-user.target" ]; - }; - }; - in - import "${pkgs.path}/nixos/lib/make-disk-image.nix" { - inherit pkgs lib; - config = nixosSystem.config; - diskSize = 20480; - format = "qcow2"; - partitionTableType = "legacy"; - # Important: include the bootloader! - installBootLoader = true; - } - EOF - - # Force rebuild by clearing any cached result - rm -f {{ nixos_storage_dir }}/nixos-image-result - - nix-build make-image.nix \ - --no-out-link \ - --arg forceRebuild true \ - -o {{ nixos_storage_dir }}/nixos-image-result - - # Return the path to the disk image - readlink -f {{ nixos_storage_dir }}/nixos-image-result/nixos.qcow2 - register: build_result - changed_when: "'Building NixOS disk image' in build_result.stdout" - when: not disk_image_exists.stat.exists - - - name: Get existing disk image path - ansible.builtin.shell: | - readlink -f {{ nixos_storage_dir }}/nixos-image-result/nixos.qcow2 - register: existing_image_path - when: disk_image_exists.stat.exists + - name: Build per-node NixOS disk image via the flake + ansible.builtin.command: + cmd: "{{ playbook_dir }}/../scripts/nixos_build_image.sh {{ nixos_config_dir }}/{{ item }}" + environment: + NIXOS_MIRROR_URL: "{{ nixos_mirror_url | default('') if (nixos_use_local_mirror | default(false) | bool) else '' }}" + loop: "{{ groups['all'] | reject('equalto', 'localhost') | list }}" + loop_control: + label: "{{ item }}" - - name: Store disk image path - ansible.builtin.set_fact: - nixos_disk_image_path: >- - {{ - build_result.stdout_lines | last | trim - if (build_result.stdout_lines is defined) - else existing_image_path.stdout | trim - }} - - - name: Copy NixOS disk image for each VM + - name: Copy per-node NixOS disk image into libvirt storage ansible.builtin.shell: | - SOURCE_IMAGE="{{ nixos_disk_image_path | default(nixos_storage_dir + '/nixos-image-result/nixos.qcow2') }}" - TARGET_IMAGE="{{ nixos_storage_dir }}/{{ item }}.qcow2" - - # Remove target if it exists and copy fresh - if [ -f "$TARGET_IMAGE" ]; then - rm -f "$TARGET_IMAGE" - fi - - cp "$SOURCE_IMAGE" "$TARGET_IMAGE" - chmod u+w "$TARGET_IMAGE" + SRC="{{ nixos_config_dir }}/{{ item }}/result/nixos.qcow2" + DST="{{ nixos_storage_dir }}/{{ item }}.qcow2" + rm -f "$DST" + cp -- "$SRC" "$DST" + chmod u+w "$DST" loop: "{{ groups['all'] | reject('equalto', 'localhost') | list }}" - when: nixos_disk_image_path is defined + loop_control: + label: "{{ item }}" - name: Generate VM wrapper scripts ansible.builtin.template: diff --git a/scripts/nixos_build_image.sh b/scripts/nixos_build_image.sh new file mode 100755 index 00000000..a4a715fc --- /dev/null +++ b/scripts/nixos_build_image.sh @@ -0,0 +1,38 @@ +#!/bin/bash +# SPDX-License-Identifier: copyleft-next-0.3.1 +# +# Build a per-node NixOS disk image from a generated kdevops flake. +# Invoked from playbooks/nixos.yml's build-vms play with a single +# argument: the per-node flake directory. +# +# Resolves the flake with a path: URL so nix does not filter the +# flake directory through git. The per-node directory lives under +# scripts/nixos-qemu/configurations/ which the nixos-qemu subtree's +# own .gitignore excludes; a plain "." or "./" flake-ref would be +# treated as git+file: and come back empty. +# +# Optional: if NIXOS_MIRROR_URL is set in the environment, the nix +# build invocation routes through that substituter in addition to +# cache.nixos.org. +set -euo pipefail + +if [ $# -ne 1 ]; then + echo "Usage: $0 <per-node-flake-directory>" >&2 + exit 1 +fi + +NODE_DIR="$1" + +if [ -f /nix/var/nix/profiles/default/etc/profile.d/nix.sh ]; then + . /nix/var/nix/profiles/default/etc/profile.d/nix.sh +fi +export PATH="/nix/var/nix/profiles/default/bin:${PATH:-/usr/local/bin:/usr/bin:/bin}" + +if [ -n "${NIXOS_MIRROR_URL:-}" ]; then + export NIX_CONFIG="substituters = ${NIXOS_MIRROR_URL} https://cache.nixos.org" +fi + +cd "$NODE_DIR" +rm -f result +nix build "path:$PWD#image" -o result +readlink -f result/nixos.qcow2 -- 2.53.0