From 8df45930a79312ab4373a44040484ef4c0fd70ec Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Thu, 8 Oct 2026 09:12:34 +0000 Subject: [PATCH 01/19] buildroot: Bump to get new genimage with dm-verity support Also, add a local patch for the growfs bit, which has not been upstreamed yet. --- buildroot | 2 +- ...rotective-first-option-for-hybrid-M.patch} | 33 +++---- ...upport-GROWFS-flag-on-GPT-partitions.patch | 94 +++++++++++++++++++ 3 files changed, 112 insertions(+), 17 deletions(-) rename patches/genimage/{0001-hdimage-put-GPT-protective-MBR-entry-first-in-hybrid.patch => 20/0001-hdimage-add-gpt-protective-first-option-for-hybrid-M.patch} (90%) create mode 100644 patches/genimage/20/0002-image-hd-Support-GROWFS-flag-on-GPT-partitions.patch diff --git a/buildroot b/buildroot index 2f8fda57a..93c7c33ba 160000 --- a/buildroot +++ b/buildroot @@ -1 +1 @@ -Subproject commit 2f8fda57a6a05043cc0bc3b749514d0e46dc63e5 +Subproject commit 93c7c33ba687337e32ef973bfa38701cd77312ce diff --git a/patches/genimage/0001-hdimage-put-GPT-protective-MBR-entry-first-in-hybrid.patch b/patches/genimage/20/0001-hdimage-add-gpt-protective-first-option-for-hybrid-M.patch similarity index 90% rename from patches/genimage/0001-hdimage-put-GPT-protective-MBR-entry-first-in-hybrid.patch rename to patches/genimage/20/0001-hdimage-add-gpt-protective-first-option-for-hybrid-M.patch index 2714e611f..3d24ca38d 100644 --- a/patches/genimage/0001-hdimage-put-GPT-protective-MBR-entry-first-in-hybrid.patch +++ b/patches/genimage/20/0001-hdimage-add-gpt-protective-first-option-for-hybrid-M.patch @@ -1,7 +1,7 @@ -From 7f50533b9e57f5e7d1e4dd47c3f43e2d6a9c8b1a Mon Sep 17 00:00:00 2001 +From 557bb6ebede417249e3df2a8fb7340a5cae0684e Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 8 Mar 2026 04:33:56 +0100 -Subject: [PATCH] hdimage: add gpt-protective-first option for hybrid MBR +Subject: [PATCH 1/2] hdimage: add gpt-protective-first option for hybrid MBR Organization: Wires In a hybrid MBR the conventional layout (used by gdisk and others) is: @@ -31,16 +31,17 @@ The default (false) preserves the existing genimage behaviour so that platforms like Raspberry Pi continue to work without any changes. Signed-off-by: Joachim Wiberg +Signed-off-by: Tobias Waldekranz --- README.rst | 10 ++++++++++ - image-hd.c | 27 ++++++++++++++++++++++----- - 2 files changed, 32 insertions(+), 5 deletions(-) + image-hd.c | 27 +++++++++++++++++++++++++-- + 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst -index 9a95f75..f45fb93 100644 +index 770bca8..a4c1a23 100644 --- a/README.rst +++ b/README.rst -@@ -480,6 +480,16 @@ Options: +@@ -481,6 +481,16 @@ Options: placed at 512 bytes (sector 1). Defaults to 1024 bytes (sector 2). :gpt-no-backup: Boolean. If true, then the backup partition table at the end of the image is not written. @@ -58,7 +59,7 @@ index 9a95f75..f45fb93 100644 random value. :fill: If this is set to true, then the image file will be filled diff --git a/image-hd.c b/image-hd.c -index 274c6ac..610d9d1 100644 +index 97e75a3..3ecdb98 100644 --- a/image-hd.c +++ b/image-hd.c @@ -46,6 +46,7 @@ struct hdimage { @@ -72,7 +73,7 @@ index 274c6ac..610d9d1 100644 @@ -152,6 +153,26 @@ static int hdimage_insert_mbr(struct image *image, struct list_head *partitions) memset(&mbr, 0, sizeof(mbr)); memcpy(&mbr.disk_signature, &hd->disksig, sizeof(hd->disksig)); - + + /* + * For hybrid MBR, optionally write the GPT protective entry (0xEE) at + * slot 0 first when gpt-protective-first = true. This matches the @@ -95,32 +96,32 @@ index 274c6ac..610d9d1 100644 + list_for_each_entry(part, partitions, list) { struct mbr_partition_entry *entry; - + @@ -176,13 +197,13 @@ static int hdimage_insert_mbr(struct image *image, struct list_head *partitions) i++; } - + - if (hd->table_type == TYPE_HYBRID) { + /* For hybrid MBR without gpt-protective-first, append 0xEE after data partitions. */ + if (hd->table_type == TYPE_HYBRID && !hd->gpt_protective_first) { struct mbr_partition_entry *entry; - + entry = &mbr.part_entry[i]; - + entry->boot = 0x00; - entry->partition_type = 0xee; entry->relative_sectors = 1; entry->total_sectors = hd->gpt_location / 512 + GPT_SECTORS - 2; -@@ -978,6 +999,7 @@ static int hdimage_setup(struct image *image, cfg_t *cfg) +@@ -979,6 +1000,7 @@ static int hdimage_setup(struct image *image, cfg_t *cfg) table_type = cfg_getstr(cfg, "partition-table-type"); hd->gpt_location = cfg_getint_suffix(cfg, "gpt-location"); hd->gpt_no_backup = cfg_getbool(cfg, "gpt-no-backup"); + hd->gpt_protective_first = cfg_getbool(cfg, "gpt-protective-first"); hd->fill = cfg_getbool(cfg, "fill"); - + if (is_block_device(imageoutfile(image))) { -@@ -1227,6 +1249,7 @@ static cfg_opt_t hdimage_opts[] = { +@@ -1229,6 +1251,7 @@ static cfg_opt_t hdimage_opts[] = { CFG_BOOL("gpt", cfg_false, CFGF_NODEFAULT), CFG_STR("gpt-location", NULL, CFGF_NONE), CFG_BOOL("gpt-no-backup", cfg_false, CFGF_NONE), @@ -128,6 +129,6 @@ index 274c6ac..610d9d1 100644 CFG_BOOL("fill", cfg_false, CFGF_NONE), CFG_END() }; --- +-- 2.43.0 diff --git a/patches/genimage/20/0002-image-hd-Support-GROWFS-flag-on-GPT-partitions.patch b/patches/genimage/20/0002-image-hd-Support-GROWFS-flag-on-GPT-partitions.patch new file mode 100644 index 000000000..32173632d --- /dev/null +++ b/patches/genimage/20/0002-image-hd-Support-GROWFS-flag-on-GPT-partitions.patch @@ -0,0 +1,94 @@ +From 13ebec3f62a9ee69c3211f68a0d71ad6678b3402 Mon Sep 17 00:00:00 2001 +From: Tobias Waldekranz +Date: Tue, 3 Mar 2026 10:02:34 +0000 +Subject: [PATCH 2/2] image-hd: Support GROWFS flag on GPT partitions +Organization: Wires + +This is an indication to the operating system that the contained +filesystem should be expanded to fill the entire partition. For more +information: + +https://uapi-group.org/specifications/specs/discoverable_partitions_specification/#partition-attribute-flags +Signed-off-by: Tobias Waldekranz +--- + README.rst | 2 ++ + genimage.c | 2 ++ + genimage.h | 1 + + image-hd.c | 2 ++ + 4 files changed, 7 insertions(+) + +diff --git a/README.rst b/README.rst +index a4c1a23..39f7566 100644 +--- a/README.rst ++++ b/README.rst +@@ -142,6 +142,7 @@ Partition options: + For hd images this can be used for the last partition. If set + the partition will fill the remaining space of the image. + :bootable: Boolean specifying whether to set the bootable flag. ++:growfs: Boolean specifying whether to set the growfs flag (only with GPT). + :hidden: Boolean specifying whether to set the hidden flag (only with GPT). + :no-automount: Boolean specifying whether to set the no-automount flag (only with GPT). + :read-only: Boolean specifying whether to set the read-only flag (only with GPT). +@@ -509,6 +510,7 @@ genimage configuration GPT FLAG + ====================== ============================== + read-only GPT_PE_FLAG_READ_ONLY (Bit 60) + bootable GPT_PE_FLAG_BOOTABLE (Bit 2) ++growfs GPT_PE_FLAG_GROWFS (Bit 59) + hidden GPT_PE_FLAG_HIDDEN (Bit 62) + no-automount GPT_PE_FLAG_NO_AUTO (Bit 63) + ====================== ============================== +diff --git a/genimage.c b/genimage.c +index 770a8c4..f804843 100644 +--- a/genimage.c ++++ b/genimage.c +@@ -102,6 +102,7 @@ static cfg_opt_t partition_opts[] = { + CFG_INT("partition-type", 0, CFGF_NONE), + CFG_BOOL("bootable", cfg_false, CFGF_NONE), + CFG_BOOL("forced-primary", cfg_false, CFGF_NONE), ++ CFG_BOOL("growfs", cfg_false, CFGF_NONE), + CFG_BOOL("read-only", cfg_false, CFGF_NONE), + CFG_BOOL("hidden", cfg_false, CFGF_NONE), + CFG_BOOL("no-automount", cfg_false, CFGF_NONE), +@@ -405,6 +406,7 @@ static int parse_partitions(struct image *image, cfg_t *imagesec) + part->partition_type = cfg_getint(partsec, "partition-type"); + part->bootable = cfg_getbool(partsec, "bootable"); + part->forced_primary = cfg_getbool(partsec, "forced-primary"); ++ part->growfs = cfg_getbool(partsec, "growfs"); + part->read_only = cfg_getbool(partsec, "read-only"); + part->hidden = cfg_getbool(partsec, "hidden"); + part->no_automount = cfg_getbool(partsec, "no-automount"); +diff --git a/genimage.h b/genimage.h +index 2587bc6..912239e 100644 +--- a/genimage.h ++++ b/genimage.h +@@ -46,6 +46,7 @@ struct partition { + cfg_bool_t bootable; + cfg_bool_t logical; + cfg_bool_t forced_primary; ++ cfg_bool_t growfs; + cfg_bool_t read_only; + cfg_bool_t hidden; + cfg_bool_t no_automount; +diff --git a/image-hd.c b/image-hd.c +index 3ecdb98..4513322 100644 +--- a/image-hd.c ++++ b/image-hd.c +@@ -106,6 +106,7 @@ ct_assert(sizeof(struct gpt_partition_entry) == 128); + #define GPT_REVISION_1_0 0x00010000 + + #define GPT_PE_FLAG_BOOTABLE (1ULL << 2) ++#define GPT_PE_FLAG_GROWFS (1ULL << 59) + #define GPT_PE_FLAG_READ_ONLY (1ULL << 60) + #define GPT_PE_FLAG_HIDDEN (1ULL << 62) + #define GPT_PE_FLAG_NO_AUTO (1ULL << 63) +@@ -522,6 +523,7 @@ static int hdimage_insert_gpt(struct image *image, struct list_head *partitions) + table[i].last_lba = htole64((partition_end(part)) / 512 - 1); + table[i].flags = + (part->bootable ? GPT_PE_FLAG_BOOTABLE : 0) | ++ (part->growfs ? GPT_PE_FLAG_GROWFS : 0) | + (part->read_only ? GPT_PE_FLAG_READ_ONLY : 0) | + (part->hidden ? GPT_PE_FLAG_HIDDEN : 0) | + (part->no_automount ? GPT_PE_FLAG_NO_AUTO : 0); +-- +2.43.0 + From 693414f1c9cb93bf69cbd56af96ce1692c26ad23 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Thu, 26 Feb 2026 20:00:07 +0000 Subject: [PATCH 02/19] qemu: Only allow custom kernel options with QEMU's native loader This is the only mode that supports the `-append` option. It has nothing to do with "not MMC". --- board/common/qemu/Config.in.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/board/common/qemu/Config.in.in b/board/common/qemu/Config.in.in index 411a69620..8ac7caf38 100644 --- a/board/common/qemu/Config.in.in +++ b/board/common/qemu/Config.in.in @@ -149,7 +149,7 @@ config IX_QEMU_HOST config IX_QEMU_APPEND string "Extra kernel options" - depends on !IX_QEMU_ROOTFS_MMC + depends on IX_QEMU_LOADER_KERNEL config IX_QEMU_EXTRA string "Extra QEMU options" From 182bc9e49b20e868ec8faa20d0930953c6777a6b Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 6 Oct 2026 08:29:13 +0000 Subject: [PATCH 03/19] factory: Add check logic In addition to triggering a factory-reset, add the ability to check for (and in the case of a marker file, optionally clear) a factory reset condition. With the upcoming DDI image support, the location of the marker file will move, so having the check implementation in factory will make it easier to keep them in sync. --- src/factory/factory.c | 44 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/src/factory/factory.c b/src/factory/factory.c index b02d90f8e..33aca3eb3 100644 --- a/src/factory/factory.c +++ b/src/factory/factory.c @@ -11,7 +11,8 @@ #include #define RESETME "/mnt/cfg/infix/.reset" -#define touch(f) mknod((f), S_IFREG|0644, 0) + +#define BOARDCHECK "/usr/libexec/infix/check-factory" int rawgetch(void) { @@ -87,12 +88,39 @@ static int run(const char *cmd) return rc; } +static int mark(void) +{ + return run("mkdir -p $(dirname " RESETME ") && touch " RESETME); +} + +static int check(int clear) +{ + struct stat st; + + if (!stat(RESETME, &st) && ((st.st_mode & S_IFMT) == S_IFREG)) { + if (clear) + unlink(RESETME); + + return 0; + } + + if (!run("grep -q 'finit.cond=factory-reset' /proc/cmdline")) + return 0; + + if (!stat(BOARDCHECK, &st) && (st.st_mode & S_IXUSR)) + return run(BOARDCHECK); + + return 1; +} + int main(int argc, char *argv[]) { struct option long_opts[] = { - { "help", 0, NULL, 'h' }, - { "no-reboot", 0, NULL, 'r' }, - { "assume-yes", 0, NULL, 'y' }, + { "check", 0, NULL, 'c' }, + { "check-clear", 0, NULL, 'C' }, + { "help", 0, NULL, 'h' }, + { "no-reboot", 0, NULL, 'r' }, + { "assume-yes", 0, NULL, 'y' }, { NULL, 0, NULL, 0 } }; int reboot = 1; @@ -100,8 +128,12 @@ int main(int argc, char *argv[]) char *tty; int c; - while ((c = getopt_long(argc, argv, "h?ry", long_opts, NULL)) != EOF) { + while ((c = getopt_long(argc, argv, "cCh?ry", long_opts, NULL)) != EOF) { switch (c) { + case 'c': + return check(0); + case 'C': + return check(1); case 'h': case '?': return usage(0); @@ -127,7 +159,7 @@ int main(int argc, char *argv[]) errx(1, "factory reset only allowed from console login!"); if (yes || yorn("Factory reset device (y/N)? ")) { - if (touch(RESETME) && errno != EEXIST) + if (mark()) err(1, "failed"); warnx("scheduled factory reset on next boot."); From e475e21fbb36e1f4b164972f2a6971ae916e7e67 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 7 Oct 2026 10:24:22 +0000 Subject: [PATCH 04/19] board/common: nginx: Ensure existance of log directory This is needed when we move away from overlay to plain mounts of /var. --- board/common/rootfs/etc/tmpfiles.d/nginx.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/board/common/rootfs/etc/tmpfiles.d/nginx.conf b/board/common/rootfs/etc/tmpfiles.d/nginx.conf index e10c9b19b..bec6a5bed 100644 --- a/board/common/rootfs/etc/tmpfiles.d/nginx.conf +++ b/board/common/rootfs/etc/tmpfiles.d/nginx.conf @@ -1,2 +1,2 @@ d /var/cache/nginx 0755 www-data www-data - +d /var/log/nginx 0755 www-data www-data From 9723b6a2251a7188490c1ad58905bcadbb9b3051 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Sun, 1 Mar 2026 18:27:29 +0000 Subject: [PATCH 05/19] image: Allow images to contribute files to the rootfs This makes it possible to ship differing system files depending on the images being built. E.g. an ITB image might expect a different /etc/fstab than a DDI. --- board/common/image/ix-image.mk | 1 + 1 file changed, 1 insertion(+) diff --git a/board/common/image/ix-image.mk b/board/common/image/ix-image.mk index 12587b228..e5ce36909 100644 --- a/board/common/image/ix-image.mk +++ b/board/common/image/ix-image.mk @@ -22,6 +22,7 @@ $(1): $$($(2)_DEPENDENCIES) ifeq ($$($(3)),y) TARGETS_ROOTFS += $(1) +BR2_ROOTFS_OVERLAY := "$$(realpath $$(pkgdir)/rootfs) $$(call qstrip,$$(BR2_ROOTFS_OVERLAY))" endif endef From 71c54e334cec2d6544dff1d08e468d2d6f989412 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Sun, 1 Mar 2026 18:30:12 +0000 Subject: [PATCH 06/19] image-itb-rootfs: Assume ownership of legacy filesystem setup This opens up for DDI images to ship their own implementation. Now that it is supported, defer factory-reset condition check to the factory binary instead of having a separate implementation here. --- .../image-itb-rootfs}/rootfs/etc/fstab | 0 .../rootfs/usr/libexec/infix/mnt | 21 +------------------ 2 files changed, 1 insertion(+), 20 deletions(-) rename board/common/{ => image/image-itb-rootfs}/rootfs/etc/fstab (100%) rename board/common/{ => image/image-itb-rootfs}/rootfs/usr/libexec/infix/mnt (96%) diff --git a/board/common/rootfs/etc/fstab b/board/common/image/image-itb-rootfs/rootfs/etc/fstab similarity index 100% rename from board/common/rootfs/etc/fstab rename to board/common/image/image-itb-rootfs/rootfs/etc/fstab diff --git a/board/common/rootfs/usr/libexec/infix/mnt b/board/common/image/image-itb-rootfs/rootfs/usr/libexec/infix/mnt similarity index 96% rename from board/common/rootfs/usr/libexec/infix/mnt rename to board/common/image/image-itb-rootfs/rootfs/usr/libexec/infix/mnt index 522d72f01..63974d5eb 100755 --- a/board/common/rootfs/usr/libexec/infix/mnt +++ b/board/common/image/image-itb-rootfs/rootfs/usr/libexec/infix/mnt @@ -22,25 +22,6 @@ err=0 mmc="" opt="-k" -# External button or bootloader changed kernel command line -check_factory() -{ - if [ -f /mnt/cfg/infix/.reset ]; then - return 0; - fi - - if grep -q 'finit.cond=factory-reset' /proc/cmdline; then - return 0; - fi - - # Add to your br2-external to extend factory-reset check - if [ ! -x /usr/libexec/infix/check-factory ]; then - return 1; - fi - - /usr/libexec/infix/check-factory -} - factory_reset() { find /sys/class/leds/ -type l -exec sh -c 'echo 100 > $0/brightness' {} \; @@ -393,7 +374,7 @@ if ! mount_rw cfg >/dev/null 2>&1; then vlibsrc= fi -if check_factory; then +if factory --check-clear; then factory_reset fi From 0874cb45c571ce79203cb5540d2f58d6816302c8 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 7 Oct 2026 10:23:12 +0000 Subject: [PATCH 07/19] image-itb-rootfs: Assume ownership of RAUC service finit config In the upcoming DDI image format, a pre-script is required. --- .../image-itb-rootfs}/rootfs/etc/finit.d/available/rauc.conf | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename board/common/{ => image/image-itb-rootfs}/rootfs/etc/finit.d/available/rauc.conf (100%) diff --git a/board/common/rootfs/etc/finit.d/available/rauc.conf b/board/common/image/image-itb-rootfs/rootfs/etc/finit.d/available/rauc.conf similarity index 100% rename from board/common/rootfs/etc/finit.d/available/rauc.conf rename to board/common/image/image-itb-rootfs/rootfs/etc/finit.d/available/rauc.conf From a9dc8660c5e7ea670b1871d1c12fc700771cd7ea Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 3 Mar 2026 07:32:40 +0000 Subject: [PATCH 08/19] image-barebox-esp: EFI System Partition with Barebox Sign the image, allowing a UEFI firmware to load run Barebox when secure boot is enabled (provided the corresponding cert is loaded in the db, of course). --- board/common/Config.in | 3 + board/common/barebox/barebox.mk | 20 +++ board/common/barebox/env/boot/net | 24 ++++ board/common/barebox/env/init/infix | 20 +++ board/common/barebox/env/menu/mainmenu | 4 + board/common/barebox/state.dts | 80 +++++++++++ board/common/common.mk | 1 + .../common/image/image-barebox-esp/Config.in | 23 ++++ .../image/image-barebox-esp/generate.sh | 44 ++++++ .../image-barebox-esp/image-barebox-esp.mk | 9 ++ board/x86_64/barebox_defconfig | 126 ++++++++++++++++++ doc/developers-guide.md | 2 +- 12 files changed, 355 insertions(+), 1 deletion(-) create mode 100644 board/common/barebox/barebox.mk create mode 100644 board/common/barebox/env/boot/net create mode 100755 board/common/barebox/env/init/infix create mode 100644 board/common/barebox/env/menu/mainmenu create mode 100644 board/common/barebox/state.dts create mode 100644 board/common/image/image-barebox-esp/Config.in create mode 100755 board/common/image/image-barebox-esp/generate.sh create mode 100644 board/common/image/image-barebox-esp/image-barebox-esp.mk create mode 100644 board/x86_64/barebox_defconfig diff --git a/board/common/Config.in b/board/common/Config.in index f81d61451..079836db9 100644 --- a/board/common/Config.in +++ b/board/common/Config.in @@ -8,6 +8,9 @@ source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-itb-gns3a/Config.in" source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-itb-rauc/Config.in" source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-ext4-rauc/Config.in" source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-itb-dl-release/Config.in" + +comment "General" +source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-barebox-esp/Config.in" source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-readme/Config.in" endmenu diff --git a/board/common/barebox/barebox.mk b/board/common/barebox/barebox.mk new file mode 100644 index 000000000..b19280a81 --- /dev/null +++ b/board/common/barebox/barebox.mk @@ -0,0 +1,20 @@ +ifeq ($(IX_IMAGE_BAREBOX_ESP),y) + +IX_BAREBOX_DIR := $(BR2_EXTERNAL_INFIX_PATH)/board/common/barebox +IX_BAREBOX_BUILD := $(BUILD_DIR)/infix-bareboxenv + +define BAREBOX_PRE_BUILD_CREATE_ENV + @$(call IXMSG,"Creating Barebox environment") + rsync -a $(IX_BAREBOX_DIR)/env/ $(IX_BAREBOX_BUILD) + dtc <$(IX_BAREBOX_DIR)/state.dts >$(IX_BAREBOX_BUILD)/state.dtb +endef +BAREBOX_PRE_BUILD_HOOKS += BAREBOX_PRE_BUILD_CREATE_ENV + +define BAREBOX_POST_BUILD_INSTALL_STATE + @$(call IXMSG,"Installing state.dtb in rootfs") + mkdir -p $(TARGET_DIR)/etc/barebox \ + && dtc <$(IX_BAREBOX_DIR)/state.dts >$(TARGET_DIR)/etc/barebox/state.dtb +endef +BAREBOX_POST_BUILD_HOOKS += BAREBOX_POST_BUILD_INSTALL_STATE + +endif diff --git a/board/common/barebox/env/boot/net b/board/common/barebox/env/boot/net new file mode 100644 index 000000000..641a199fd --- /dev/null +++ b/board/common/barebox/env/boot/net @@ -0,0 +1,24 @@ +#!/bin/sh + +if dhcp $1; then + if [ -z "${global.dhcp.bootfile}" ]; then + echo "net: ERROR: DHCP did not supply a bootfile" + exit 1 + fi +else + echo "net: ERROR: DHCP Failed" + exit 1 +fi + +echo "net: Downloading ${global.dhcp.bootfile}..." +if tftp ${global.dhcp.bootfile} /initrd; then +else + echo "net: ERROR: Download failed" + exit 1 +fi + +global bootm.initrd=/initrd +dmsetup loop initrd /initrd && boot /dev/initrd + +echo "net: ERROR: Unable to boot from ${global.dhcp.bootfile}" +exit 1 diff --git a/board/common/barebox/env/init/infix b/board/common/barebox/env/init/infix new file mode 100755 index 000000000..d83079a9e --- /dev/null +++ b/board/common/barebox/env/init/infix @@ -0,0 +1,20 @@ +#!/bin/sh + +global allow_color=1 +global model=efi +export PS1="\e[1;32mbarebox@\h\e[0m:\e[1;36m\w\e[0m# " + +global autoboot_timeout=1 +global boot.default="bootchooser storage" +global bootchooser.reset_attempts="all-zero" +global bootchooser.retry=1 +global bootchooser.state_prefix="state.bootstate" +global bootchooser.targets="internal-primary internal-secondary net" + +global blspec.require_trust=${efi.secure_mode} +global ddi.require_trust=${efi.secure_mode} + +keys -l os @efi + +lvm activate + diff --git a/board/common/barebox/env/menu/mainmenu b/board/common/barebox/env/menu/mainmenu new file mode 100644 index 000000000..6b8c2fc52 --- /dev/null +++ b/board/common/barebox/env/menu/mainmenu @@ -0,0 +1,4 @@ +#!/bin/sh + +boot -m +reset diff --git a/board/common/barebox/state.dts b/board/common/barebox/state.dts new file mode 100644 index 000000000..08bf6cd8e --- /dev/null +++ b/board/common/barebox/state.dts @@ -0,0 +1,80 @@ +/dts-v1/; + +/ { + aliases { + state = &state; + }; + + partitions { + compatible = "fixed-partitions"; + + statepart: state { + partuuid = "80ca5554-c020-11f1-ba4d-b3c49d5dbf47"; + }; + }; + + state: state { + compatible = "barebox,state"; + magic = <0x80ca5554>; + backend-type = "raw"; + backend = <&statepart>; + backend-stridesize = <0x200>; + + bootstate { + #address-cells = <1>; + #size-cells = <1>; + + internal-primary { + #address-cells = <1>; + #size-cells = <1>; + remaining_attempts@0 { + reg = <0x0 0x4>; + type = "uint32"; + default = <3>; + }; + priority@4 { + reg = <0x4 0x4>; + type = "uint32"; + default = <20>; + }; + }; + + internal-secondary { + #address-cells = <1>; + #size-cells = <1>; + remaining_attempts@8 { + reg = <0x8 0x4>; + type = "uint32"; + default = <3>; + }; + priority@c { + reg = <0xc 0x4>; + type = "uint32"; + default = <10>; + }; + }; + + net { + #address-cells = <1>; + #size-cells = <1>; + remaining_attempts@10 { + reg = <0x10 0x4>; + type = "uint32"; + default = <0>; + }; + priority@14 { + reg = <0x14 0x4>; + type = "uint32"; + default = <0>; + }; + }; + + last_chosen@18 { + #address-cells = <1>; + #size-cells = <1>; + reg = <0x18 0x4>; + type = "uint32"; + }; + }; + }; +}; diff --git a/board/common/common.mk b/board/common/common.mk index e0586379d..a570f09d4 100644 --- a/board/common/common.mk +++ b/board/common/common.mk @@ -1,3 +1,4 @@ +include $(BR2_EXTERNAL_INFIX_PATH)/board/common/barebox/barebox.mk include $(BR2_EXTERNAL_INFIX_PATH)/board/common/image/image.mk include $(BR2_EXTERNAL_INFIX_PATH)/board/common/qemu/qemu.mk diff --git a/board/common/image/image-barebox-esp/Config.in b/board/common/image/image-barebox-esp/Config.in new file mode 100644 index 000000000..af12ba9d6 --- /dev/null +++ b/board/common/image/image-barebox-esp/Config.in @@ -0,0 +1,23 @@ +menuconfig IX_IMAGE_BAREBOX_ESP + bool "Barebox EFI System Partition" + depends on BR2_TARGET_BAREBOX + select BR2_PACKAGE_DT_UTILS + help + An EFI System Partition (ESP) with Barebox installed. + +config IX_IMAGE_BAREBOX_ESP_KEY + string "signing key" + depends on IX_IMAGE_BAREBOX_ESP + default "${BR2_EXTERNAL_INFIX_PATH}/board/common/signing-keys/development/infix.key" + help + Path to the private RSA key, in PKCS#8 format, used to sign + the Barebox EFI payload. + +config IX_IMAGE_BAREBOX_ESP_CERT + string "signing cert" + depends on IX_IMAGE_BAREBOX_ESP + default "${BR2_EXTERNAL_INFIX_PATH}/board/common/signing-keys/development/infix.crt" + help + Path to the X509 certificate whose associated public key can + validate the authenticity of the generated signature. + diff --git a/board/common/image/image-barebox-esp/generate.sh b/board/common/image/image-barebox-esp/generate.sh new file mode 100755 index 000000000..acec48910 --- /dev/null +++ b/board/common/image/image-barebox-esp/generate.sh @@ -0,0 +1,44 @@ +#!/bin/sh + +set -e + +case "$BR2_ARCH" in + x86_64) + BOOT_EFI=BOOTX64.EFI + ;; + *) + echo "Unknown EFI boot path for $BR2_ARCH" >&2 + exit 1 + ;; +esac + +mkdir -p "${WORKDIR}"/root +rm -rf "${WORKDIR}"/tmp +mkdir -p "${WORKDIR}"/tmp + +sbsign \ + --key "${KEY}" \ + --cert "${CERT}" \ + --output "${BINARIES_DIR}"/barebox.efi.signed \ + "${BINARIES_DIR}"/barebox.efi + +cat <"${WORKDIR}"/genimage.cfg +image barebox-esp.vfat { + size = "16M" + vfat { + file EFI/BOOT/$BOOT_EFI { + image = $BINARIES_DIR/barebox.efi.signed + } + } +} + +# Silence genimage warnings +config {} +EOF + +genimage \ + --tmppath "${WORKDIR}"/tmp \ + --rootpath "${WORKDIR}"/root \ + --inputpath "${WORKDIR}" \ + --outputpath "${BINARIES_DIR}" \ + --config "${WORKDIR}"/genimage.cfg diff --git a/board/common/image/image-barebox-esp/image-barebox-esp.mk b/board/common/image/image-barebox-esp/image-barebox-esp.mk new file mode 100644 index 000000000..758448ef1 --- /dev/null +++ b/board/common/image/image-barebox-esp/image-barebox-esp.mk @@ -0,0 +1,9 @@ +################################################################################ +# +# image-barebox-esp +# +################################################################################ + +IMAGE_BAREBOX_ESP_CONFIG_VARS := KEY CERT + +$(eval $(ix-image)) diff --git a/board/x86_64/barebox_defconfig b/board/x86_64/barebox_defconfig new file mode 100644 index 000000000..b4867734c --- /dev/null +++ b/board/x86_64/barebox_defconfig @@ -0,0 +1,126 @@ +CONFIG_NAME="efi_defconfig" +CONFIG_NVVAR=y +CONFIG_MMU=y +CONFIG_HUSH_FANCY_PROMPT=y +CONFIG_CMDLINE_EDITING=y +CONFIG_AUTO_COMPLETE=y +CONFIG_MENU=y +# CONFIG_TIMESTAMP is not set +CONFIG_BOOTM_SHOW_TYPE=y +CONFIG_BOOTM_VERBOSE=y +CONFIG_BOOTM_INITRD=y +CONFIG_BLSPEC=y +CONFIG_BOOT_DDI=y +CONFIG_FLEXIBLE_BOOTARGS=y +CONFIG_CONSOLE_ACTIVATE_ALL=y +CONFIG_PARTITION_DISK_EFI=y +# CONFIG_ENV_HANDLING is not set +CONFIG_DEFAULT_ENVIRONMENT=y +CONFIG_STATE=y +CONFIG_BOOTCHOOSER=y +CONFIG_RESET_SOURCE=y +CONFIG_DEFAULT_LOGLEVEL=4 +CONFIG_DEBUG_LL=y +# CONFIG_EFI_HANDOVER_PROTOCOL is not set +CONFIG_EFI_PAYLOAD_BOOTM=y +CONFIG_EFI_PAYLOAD_STATE_LAYOUT_PATH="/env/state.dtb" +CONFIG_CMD_DMESG=y +CONFIG_LONGHELP=y +CONFIG_CMD_IOMEM=y +CONFIG_CMD_IMD=y +CONFIG_CMD_MEMINFO=y +CONFIG_CMD_BOOT=y +CONFIG_CMD_GO=y +CONFIG_CMD_LOADB=y +CONFIG_CMD_RESET=y +CONFIG_CMD_UIMAGE=y +CONFIG_CMD_BOOTCHOOSER=y +CONFIG_CMD_PARTITION=y +CONFIG_CMD_AUTOMOUNT=y +CONFIG_CMD_DMSETUP=y +CONFIG_CMD_VERITYSETUP=y +CONFIG_CMD_LVM=y +CONFIG_CMD_NV=y +CONFIG_CMD_EXPORT=y +CONFIG_CMD_GLOBAL=y +CONFIG_CMD_PRINTENV=y +CONFIG_CMD_MAGICVAR=y +CONFIG_CMD_MAGICVAR_HELP=y +CONFIG_CMD_BASENAME=y +CONFIG_CMD_DIRNAME=y +CONFIG_CMD_FILETYPE=y +CONFIG_CMD_LN=y +CONFIG_CMD_MD5SUM=y +CONFIG_CMD_READLINK=y +CONFIG_CMD_UNCOMPRESS=y +CONFIG_CMD_GETOPT=y +CONFIG_CMD_LET=y +CONFIG_CMD_MSLEEP=y +CONFIG_CMD_READF=y +CONFIG_CMD_SLEEP=y +CONFIG_CMD_DHCP=y +CONFIG_CMD_HOST=y +CONFIG_NET_CMD_IFUP=y +CONFIG_CMD_PING=y +CONFIG_CMD_TFTP=y +CONFIG_CMD_ECHO_E=y +CONFIG_CMD_EDIT=y +CONFIG_CMD_MENU=y +CONFIG_CMD_MENUTREE=y +CONFIG_CMD_READLINE=y +CONFIG_CMD_TIMEOUT=y +CONFIG_CMD_CRC=y +CONFIG_CMD_CRC_CMP=y +CONFIG_CMD_MM=y +CONFIG_CMD_DETECT=y +CONFIG_CMD_FLASH=y +CONFIG_CMD_POWEROFF=y +CONFIG_CMD_WD=y +CONFIG_CMD_KEYS=y +CONFIG_CMD_PKCS7=y +CONFIG_CMD_2048=y +CONFIG_CMD_BAREBOX_UPDATE=y +CONFIG_CMD_OF_DIFF=y +CONFIG_CMD_OF_NODE=y +CONFIG_CMD_OF_PROPERTY=y +CONFIG_CMD_OFTREE=y +CONFIG_CMD_TIME=y +CONFIG_NET=y +CONFIG_NET_NETCONSOLE=y +CONFIG_DEEP_PROBE_DEFAULT=y +CONFIG_OFDEVICE=y +CONFIG_DRIVER_SERIAL_EFI_STDIO=y +CONFIG_DRIVER_NET_EFI_SNP=y +# CONFIG_SPI is not set +CONFIG_DISK=y +CONFIG_DISK_WRITE=y +CONFIG_DM_BLK=y +CONFIG_DM_BLK_LINEAR=y +CONFIG_DM_BLK_VERITY=y +CONFIG_DM_LVM=y +CONFIG_USB_HOST=y +CONFIG_USB_EFI_IO_PROTOCOL=y +CONFIG_VIDEO=y +CONFIG_FRAMEBUFFER_CONSOLE=y +CONFIG_DRIVER_VIDEO_EFI_GOP=y +CONFIG_FINTEK_SUPERIO=y +CONFIG_SMSC_SUPERIO=y +CONFIG_WATCHDOG=y +CONFIG_WATCHDOG_EFI=y +CONFIG_F71808E_WDT=y +CONFIG_ITCO_WDT=y +# CONFIG_PINCTRL is not set +CONFIG_PCI_EFI=y +CONFIG_FS_EXT4=y +CONFIG_FS_TFTP=y +CONFIG_FS_TFTP_MAX_WINDOW_SIZE=128 +CONFIG_FS_NFS=y +CONFIG_FS_EFI=y +CONFIG_FS_EFIVARFS=y +CONFIG_FS_FAT=y +CONFIG_FS_FAT_WRITE=y +CONFIG_FS_SQUASHFS=y +CONFIG_CRYPTO_RSA=y +CONFIG_CRYPTO_ECDSA=y +CONFIG_CRYPTO_EFI_DB=y +CONFIG_ZLIB=y diff --git a/doc/developers-guide.md b/doc/developers-guide.md index b1325692c..bb44bf262 100644 --- a/doc/developers-guide.md +++ b/doc/developers-guide.md @@ -73,7 +73,7 @@ $ sudo apt install bc binutils build-essential bzip2 cpio \ libncurses-dev libssl-dev perl patch \ python3 rsync sed tar unzip wget \ autopoint bison flex autoconf automake \ - mtools + mtools sbsigntool ``` To build an Infix image; select the target and then make: From e1999d3057cdb67ea7f871cea3f8b35435476c52 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Thu, 4 Dec 2025 08:01:18 +0000 Subject: [PATCH 09/19] image-ddi: Add Discoverable Disk Image (DDI) --- board/common/Config.in | 5 + board/common/image/image-ddi/Config.in | 40 +++ board/common/image/image-ddi/generate.sh | 109 ++++++++ board/common/image/image-ddi/image-ddi.mk | 10 + .../image-ddi/rootfs/boot/efi-backup/.empty | 0 .../image/image-ddi/rootfs/boot/efi/.empty | 0 .../etc/finit.d/available/mark-boot-ok.conf | 1 + .../rootfs/etc/finit.d/available/rauc.conf | 5 + .../etc/finit.d/enabled/mark-boot-ok.conf | 1 + board/common/image/image-ddi/rootfs/etc/fstab | 18 ++ .../image/image-ddi/rootfs/mnt/rw/.empty | 0 .../image-ddi/rootfs/usr/libexec/infix/mnt | 253 ++++++++++++++++++ .../rootfs/usr/libexec/infix/rauc-config-gen | 88 ++++++ board/common/image/ix-image.mk | 1 + board/common/post-build.sh | 5 + board/x86_64/rootfs/loader/entries/os.conf | 4 + external.mk | 16 ++ package/factory/factory.mk | 5 +- src/factory/factory.c | 8 +- 19 files changed, 565 insertions(+), 4 deletions(-) create mode 100644 board/common/image/image-ddi/Config.in create mode 100755 board/common/image/image-ddi/generate.sh create mode 100644 board/common/image/image-ddi/image-ddi.mk create mode 100644 board/common/image/image-ddi/rootfs/boot/efi-backup/.empty create mode 100644 board/common/image/image-ddi/rootfs/boot/efi/.empty create mode 100644 board/common/image/image-ddi/rootfs/etc/finit.d/available/mark-boot-ok.conf create mode 100644 board/common/image/image-ddi/rootfs/etc/finit.d/available/rauc.conf create mode 120000 board/common/image/image-ddi/rootfs/etc/finit.d/enabled/mark-boot-ok.conf create mode 100644 board/common/image/image-ddi/rootfs/etc/fstab create mode 100644 board/common/image/image-ddi/rootfs/mnt/rw/.empty create mode 100755 board/common/image/image-ddi/rootfs/usr/libexec/infix/mnt create mode 100755 board/common/image/image-ddi/rootfs/usr/libexec/infix/rauc-config-gen create mode 100644 board/x86_64/rootfs/loader/entries/os.conf diff --git a/board/common/Config.in b/board/common/Config.in index 079836db9..e82b5fd45 100644 --- a/board/common/Config.in +++ b/board/common/Config.in @@ -1,5 +1,10 @@ + menu "Images" +comment "DDI Based" +source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-ddi/Config.in" + +comment "ITB Based" source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-itb-rootfs/Config.in" source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-itb-aux/Config.in" source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-itb-boot/Config.in" diff --git a/board/common/image/image-ddi/Config.in b/board/common/image/image-ddi/Config.in new file mode 100644 index 000000000..75b6a1c94 --- /dev/null +++ b/board/common/image/image-ddi/Config.in @@ -0,0 +1,40 @@ +menuconfig IX_IMAGE_DDI + bool "Discoverable Disk Image (DDI)" + select BR2_TARGET_ROOTFS_SQUASHFS + select BR2_PACKAGE_BTRFS_PROGS + select BR2_PACKAGE_CRYPTSETUP + select BR2_PACKAGE_GPTFDISK + select BR2_PACKAGE_GPTFDISK_SGDISK + select BR2_PACKAGE_LVM2 + select BR2_PACKAGE_MULTIPATH_TOOLS + select BR2_PACKAGE_UTIL_LINUX_BINARIES + select BR2_PACKAGE_HOST_GENIMAGE + help + Create a Discoverable Disk Image containing: + - SquashFS of the root filesystem + - dm-verity hash tree of the root filesystem + - DDI compliant JSON signature object of the hash tree + + https://uapi-group.org/specifications/specs/discoverable_disk_image/ + +config IX_IMAGE_DDI_KEY + string "signing key" + depends on IX_IMAGE_DDI + default "${BR2_EXTERNAL_INFIX_PATH}/board/common/signing-keys/development/infix.key" + help + Path to the private RSA key, in PKCS#8 format, used to sign + the dm-verity hash tree. + +config IX_IMAGE_DDI_CERT + string "signing cert" + depends on IX_IMAGE_DDI + default "${BR2_EXTERNAL_INFIX_PATH}/board/common/signing-keys/development/infix.crt" + help + Path to the X509 certificate whose associated public key can + validate the authenticity of the generated signature. + + The SHA256 fingerprint of this certificate is recorded + alongside the standard PKCS#7 signature in the JSON object, + which can be useful to locate corresponding key on systems + that lack full X509 parsing capabilities. + diff --git a/board/common/image/image-ddi/generate.sh b/board/common/image/image-ddi/generate.sh new file mode 100755 index 000000000..13d0e9723 --- /dev/null +++ b/board/common/image/image-ddi/generate.sh @@ -0,0 +1,109 @@ +#!/bin/sh + +# Generate the DDI in two phases. First we create the verity hash tree +# and signature, then we collect them together with the rootfs in the +# ddi. The reason for this is that genimage (at least up to v19) can +# not infer the size of a partition if the image to be placed in it is +# created by the same genimage instance. With the two-phase approach, +# we work around that problem and can create a DDI without any wasted +# space. + +set -e + +uuidfmt() +{ + echo $1 | \ + sed -E 's/^([0-9a-fA-F]{8})([0-9a-fA-F]{4})([0-9a-fA-F]{4})([0-9a-fA-F]{4})([0-9a-fA-F]{12})$/\1-\2-\3-\4-\5/' +} + +rm -rf "${WORKDIR}"/tmp +mkdir -p "${WORKDIR}"/tmp "${WORKDIR}"/verity + +cat <"${WORKDIR}"/genimage-verity.cfg +image rootfs.verity { + verity { + image = "rootfs.squashfs" + } +} + +image rootfs.verity-sig { + verity-sig { + image = "rootfs.verity" + cert = "${CERT}" + key = "${KEY}" + } +} + +config {} +EOF + +genimage \ + --loglevel 1 \ + --tmppath "${WORKDIR}"/tmp \ + --rootpath "${WORKDIR}" \ + --inputpath "${BINARIES_DIR}" \ + --outputpath "${WORKDIR}"/verity \ + --config "${WORKDIR}"/genimage-verity.cfg + +case "${BR2_ARCH}" in + "x86_64") + arch=x86-64 + ;; + *) + echo "ERROR: missing mapping from ${BR2_ARCH} to genimage arch" >&2 + exit 1 + ;; +esac + +# As described in the DPS spec, setting the data partition's UUID to +# the first 128 bits of the roothash and the verity partition's UUID +# to the last 128 bits of the roothash allows for auto-discovery of +# the partitions based only on the roothash. +roothash=$(tr -d '\000' <"${WORKDIR}"/verity/rootfs.verity-sig | jq -r .rootHash) +rootuuid=$(uuidfmt $(echo $roothash | cut -c -32)) +hashuuid=$(uuidfmt $(echo $roothash | rev | cut -c -32 | rev)) + +# Make sure that the final image size is aligned to a 32k byte +# boundary. This lines up with the extent size used by lvm-mkinternal, +# which means that we won't get GPT location warnings from Barebox +# when we activate the LV. +ALIGN_K=32 +blks=$((2+32+32)) +blks=$((blks + (($(stat -c%s "${BINARIES_DIR}"/rootfs.squashfs) + 511) >> 9))) +blks=$((blks + (($(stat -c%s "${WORKDIR}"/verity/rootfs.verity) + 511) >> 9))) +blks=$((blks + (($(stat -c%s "${WORKDIR}"/verity/rootfs.verity-sig) + 511) >> 9))) +size=$((((blks << 9) + ((ALIGN_K << 10) - 1)) & ~((ALIGN_K << 10) - 1))) + +cat <"${WORKDIR}"/genimage-ddi.cfg +image ${ARTIFACT}.raw { + size = ${size} + hdimage { + partition-table-type = "gpt" + } + + partition root { + partition-type-uuid = "root-${arch}" + partition-uuid = "$rootuuid" + image = "rootfs.squashfs" + } + partition root-verity { + partition-type-uuid = "root-${arch}-verity" + partition-uuid = "$hashuuid" + image = "${WORKDIR}/verity/rootfs.verity" + } + partition root-verity-sig { + partition-type-uuid = "root-${arch}-verity-sig" + image = "${WORKDIR}/verity/rootfs.verity-sig" + } +} + +config {} +EOF + +genimage \ + --loglevel 1 \ + --tmppath "${WORKDIR}"/tmp \ + --rootpath "${WORKDIR}" \ + --inputpath "${BINARIES_DIR}" \ + --outputpath "${BINARIES_DIR}" \ + --config "${WORKDIR}"/genimage-ddi.cfg diff --git a/board/common/image/image-ddi/image-ddi.mk b/board/common/image/image-ddi/image-ddi.mk new file mode 100644 index 000000000..bd51942f3 --- /dev/null +++ b/board/common/image/image-ddi/image-ddi.mk @@ -0,0 +1,10 @@ +################################################################################ +# +# image-ddi +# +################################################################################ + +IMAGE_DDI_DEPENDENCIES := host-dracut host-genimage rootfs-squashfs +IMAGE_DDI_CONFIG_VARS := KEY CERT + +$(eval $(ix-image)) diff --git a/board/common/image/image-ddi/rootfs/boot/efi-backup/.empty b/board/common/image/image-ddi/rootfs/boot/efi-backup/.empty new file mode 100644 index 000000000..e69de29bb diff --git a/board/common/image/image-ddi/rootfs/boot/efi/.empty b/board/common/image/image-ddi/rootfs/boot/efi/.empty new file mode 100644 index 000000000..e69de29bb diff --git a/board/common/image/image-ddi/rootfs/etc/finit.d/available/mark-boot-ok.conf b/board/common/image/image-ddi/rootfs/etc/finit.d/available/mark-boot-ok.conf new file mode 100644 index 000000000..ac2c5cdbe --- /dev/null +++ b/board/common/image/image-ddi/rootfs/etc/finit.d/available/mark-boot-ok.conf @@ -0,0 +1 @@ +task name:mark-boot-ok [2345] rauc status mark-good -- diff --git a/board/common/image/image-ddi/rootfs/etc/finit.d/available/rauc.conf b/board/common/image/image-ddi/rootfs/etc/finit.d/available/rauc.conf new file mode 100644 index 000000000..d79a3eac4 --- /dev/null +++ b/board/common/image/image-ddi/rootfs/etc/finit.d/available/rauc.conf @@ -0,0 +1,5 @@ +set G_MESSAGES_DEBUG=nocolor +service [2345] notify:systemd \ + pre:/usr/libexec/infix/rauc-config-gen \ + env:-/etc/default/rauc \ + rauc service $RAUC_ARGS -- Software update service diff --git a/board/common/image/image-ddi/rootfs/etc/finit.d/enabled/mark-boot-ok.conf b/board/common/image/image-ddi/rootfs/etc/finit.d/enabled/mark-boot-ok.conf new file mode 120000 index 000000000..2bdda4b62 --- /dev/null +++ b/board/common/image/image-ddi/rootfs/etc/finit.d/enabled/mark-boot-ok.conf @@ -0,0 +1 @@ +../available/mark-boot-ok.conf \ No newline at end of file diff --git a/board/common/image/image-ddi/rootfs/etc/fstab b/board/common/image/image-ddi/rootfs/etc/fstab new file mode 100644 index 000000000..cf9d52927 --- /dev/null +++ b/board/common/image/image-ddi/rootfs/etc/fstab @@ -0,0 +1,18 @@ +tmpfs /run tmpfs mode=0755,nosuid,nodev 0 0 +tmpfs /media tmpfs mode=1755,nosuid,nodev 0 0 +tmpfs /mnt/tmp tmpfs defaults 0 0 + +/dev/disk/by-partuuid/82bb760e-c08e-11f1-a791-7bd312fe0c17 /boot/efi vfat defaults 0 0 +/dev/disk/by-partuuid/835b97d8-c08e-11f1-a2f7-73ad4bbf39e9 /boot/efi-backup vfat defaults 0 0 + +# Locate and mount /cfg, /home, /root and /var from the internal +# volume, wherever that is located - or, as a last resort, setup tmpfs +# mounts at these points. Setup an ephemeral writable overlay on /etc. +/usr/libexec/infix/mnt# /mnt/rw helper none 0 0 + +# Optional, nice-to-have mounts +debugfs /sys/kernel/debug debugfs nofail 0 0 +tracefs /sys/kernel/tracing tracefs nofail 0 0 +efivarfs /sys/firmware/efi/efivars efivarfs nofail 0 0 +cfgfs /config configfs nofail,noauto 0 0 +host /mnt/host 9p nofail,noauto,cache=none,msize=16384 0 0 diff --git a/board/common/image/image-ddi/rootfs/mnt/rw/.empty b/board/common/image/image-ddi/rootfs/mnt/rw/.empty new file mode 100644 index 000000000..e69de29bb diff --git a/board/common/image/image-ddi/rootfs/usr/libexec/infix/mnt b/board/common/image/image-ddi/rootfs/usr/libexec/infix/mnt new file mode 100755 index 000000000..7bec8c915 --- /dev/null +++ b/board/common/image/image-ddi/rootfs/usr/libexec/infix/mnt @@ -0,0 +1,253 @@ +#!/bin/sh +# Called from /etc/fstab to ensure we have something writable mounted +# at /cfg, /home, /root, and /var. +# +# In the normal case, the "rw" LV from the "internal" LVM VG is used +# to back all persistant storage. In the event that "internal" is +# present, but the "rw" LV is missing, one will be provisioned, along +# with the required BTRFS subvolumes. +# +# In all other scenarios where the subvolumes are not available, the +# system will fall back to temporary storage for all mount points. +# +# For /etc, a writable, tmpfs backed, layer is always overlayed on top +# of the read-only contents from the OS image. + +set -e + +nm=$(basename "$0") +need_restore= +code=0 + +logcrit() +{ + logger -k -t "$nm" -p user.crit "$@" +} + +lognote() +{ + logger -k -t "$nm" -p user.notice "$@" +} + +status() +{ + GREEN="$(printf '\033[1;32m')" + RED="$(printf '\033[1;31m')" + YELLOW="$(printf '\033[1;33m')" + BOLD="$(printf '\033[1m')" + RESET="$(printf '\033[0m')" + + case $1 in + 0) color=$GREEN; text=' OK ' ;; + 1) color=$RED; text='FAIL' ;; + 2) color=$YELLOW;text='WARN' ;; + *) color=$YELLOW;text=' ⋯ ' ;; + esac + + printf '%s[%s%s%s%s]%s ' "$BOLD" "$color" "$text" "$RESET" "$BOLD" "$RESET" +} + +print_start() +{ + printf '\r\033[K%s%s' "$(status 3)" "$*" > /dev/console + need_restore=YES +} + +print_end() +{ + rc=$1; shift + if [ $# -gt 0 ]; then + printf '\r\033[K%s%s\n' "$(status "$rc")" "$*" > /dev/console + else + printf '\r%s\n' "$(status "$rc")" > /dev/console + fi +} + +# Restore Finit's original progress message so its [ OK ] appears correctly +print_restore() +{ + [ "$need_restore" ] || return 0 + print_start "Mounting filesystems from /etc/fstab" +} + +internal_rw_factory_reset() +{ + factory --check-clear || return 0 + [ -b /dev/internal/rw ] || return 0 + + print_start "Resetting to factory defaults" + lognote "Resetting to factory defaults" + + lvm lvremove -y internal/rw || { + logcrit "Unable to remove rw volume" + print_end 1 + return 0 + } + + lognote "Factory reset complete" + print_end 0 +} + +subvols="cfg var root home" + +internal_rw_provision() +{ + # The only condition under which we do BTRFS provisioning is when + # the LV is absent. In all other failure scenarios: fall back to + # tmpfs and let the user sort it out. This way we do not risk any + # loss of data. + [ -b /dev/internal/rw ] && return 0 + + print_start "Provisioning persistent storage on internal volume" + + lvm lvcreate -y --name rw --extents 50%FREE internal || { + logcrit "No rw LV available, nor could it be created" + print_end 1 + return 1 + } + + mkfs.btrfs -L rw /dev/internal/rw || { + logcrit "Could not create BTRFS on rw LV" + print_end 1 + return 1 + } + + mount -t btrfs /dev/internal/rw /mnt/rw || { + logcrit "Failed to mount rw" + print_end 1 + return 1 + } + + for subvol in $subvols; do + btrfs subvolume create /mnt/rw/@"$subvol" || { + logcrit "Failed to create @$subvol on rw" + print_end 1 + return 1 + } + done + + umount /mnt/rw || { + logcrit "Failed to unmount rw" + print_end 1 + return 1 + } + + print_end 0 +} + +internal_secondary_provision() +{ + local _free _size + + # On the first system boot, if a primary image is available, but a + # secondary one is not, provision a duplicate of primary as the + # secondary image. This let's the distribution disk image be + # small, yet automatically deploys a redundant setup. + [ -f /dev/firstboot ] || return 0 + [ -b /dev/internal/primary ] || return 0 + [ -b /dev/internal/secondary ] && return 0 + + print_start "Provisioning redundant image" + + _free=$(lvm vgs --reportformat=json --units b internal \ + | jq -r .report[0].vg[0].vg_free \ + | tr -d B) + _size=$(lvs --reportformat=json --units b internal/primary \ + | jq -r .report[0].lv[0].lv_size \ + | tr -d B) + + [ "$_free" -ge "$_size" ] || { + logcrit "Skipping redundant $((_size >> 20))MB image, $((_free >> 20))MB available" + print_end 1 + return 0 + } + + lvm lvcreate --name secondary --size "${_size}B" internal || { + logcrit "Unable to create secondary LV" + print_end 1 + return 1 + } + + dd if=/dev/internal/primary of=/dev/internal/secondary bs=1M status=none || { + logcrit "Redundant image replication failed" + print_end 1 + return 1 + } + + kpartx -a -p-part /dev/internal/secondary || { + logcrit "Failed to setup partitions from replicated secondary image" + } + + print_end 0 +} + +internal_activate() +{ + lvm vgs internal || { + logcrit "No internal volume available" + return 1 + } + + lvm vgchange -ay internal || { + logcrit "Unable to bring internal volume online" + return 1 + } + + internal_rw_factory_reset || return 1 + internal_rw_provision || return 1 + internal_secondary_provision || return 1 +} + +internal_rw_mount() +{ + [ -b /dev/internal/rw ] || return 1 + + for subvol in $subvols; do + mount -t btrfs /dev/internal/rw /"$subvol" -o subvol=@"$subvol" || { + logcrit "Failed to mount /$subvol" + return 1 + } + done +} + +tmpfs_rw_mount() +{ + for subvol in $subvols; do + mkdir -p -m 0755 /mnt/tmp/"$subvol" + mount /mnt/tmp/"$subvol" /"$subvol" -o bind || { + logcrit "Failed to mount temporary /$subvol" + return 1 + } + done +} + +tmpfs_etc_mount() +{ + mkdir -p -m 0755 /mnt/tmp/etc/ + tar c -C /etc . | tar x -C /mnt/tmp/etc + mount /mnt/tmp/etc /etc -o bind +} + +exec >/mnt/tmp/mnt.log 2>&1 + +tmpfs_etc_mount || { + code=1 + logcrit "Failed to make /etc writable" +} + +internal_activate || { + print_end 1 "Activating internal volume" +} + +internal_rw_mount || { + code=1 + print_end 1 "Mounting persistent storage from internal volume" + logcrit "Internal volume not found, falling back to tmpfs" + + print_start "Falling back to ephemeral tmpfs storage!" + tmpfs_rw_mount + print_end $? +} + +print_restore +exit $code diff --git a/board/common/image/image-ddi/rootfs/usr/libexec/infix/rauc-config-gen b/board/common/image/image-ddi/rootfs/usr/libexec/infix/rauc-config-gen new file mode 100755 index 000000000..4afc74e5f --- /dev/null +++ b/board/common/image/image-ddi/rootfs/usr/libexec/infix/rauc-config-gen @@ -0,0 +1,88 @@ +#!/bin/sh + +. /etc/os-release + +{ + cat </etc/rauc/system.conf + +mkdir -p /var/lib/rauc + +[ -f /var/lib/rauc/central.raucs ] && exit + +unixepoch=$(date -d1970-01-01 -u +%FT%TZ) + +{ + cat </var/lib/rauc/central.raucs + diff --git a/board/common/image/ix-image.mk b/board/common/image/ix-image.mk index e5ce36909..d026d1c40 100644 --- a/board/common/image/ix-image.mk +++ b/board/common/image/ix-image.mk @@ -13,6 +13,7 @@ $(1): $$($(2)_DEPENDENCIES) WORKDIR=$$(BUILD_DIR)/$(1) \ BINARIES_DIR=$$(BINARIES_DIR) \ TARGET_DIR=$$(TARGET_DIR) \ + BR2_ARCH=$$(BR2_ARCH) \ BR2_EXTERNAL_INFIX_PATH=$$(BR2_EXTERNAL_INFIX_PATH) \ ARTIFACT=$$(INFIX_ARTIFACT) \ COMPATIBLE=$$(IX_COMPATIBLE) \ diff --git a/board/common/post-build.sh b/board/common/post-build.sh index 5a94fe885..4d4a709e0 100755 --- a/board/common/post-build.sh +++ b/board/common/post-build.sh @@ -91,6 +91,11 @@ rm -f "$TARGET_DIR/etc/os-release" fi } > "$TARGET_DIR/etc/os-release" +if [ -f "$TARGET_DIR"/loader/entries/os.conf ]; then + sed -i "s/@TITLE@/$IX_NAME $INFIX_VERSION/" \ + "$TARGET_DIR"/loader/entries/os.conf +fi + echo "$IX_TAGLINE $INFIX_VERSION -- $(date +"%b %e %H:%M %Z %Y")" > "$TARGET_DIR/etc/version" ixmsg "Creating /etc/version: $(cat "$TARGET_DIR/etc/version")" diff --git a/board/x86_64/rootfs/loader/entries/os.conf b/board/x86_64/rootfs/loader/entries/os.conf new file mode 100644 index 000000000..cd19524a7 --- /dev/null +++ b/board/x86_64/rootfs/loader/entries/os.conf @@ -0,0 +1,4 @@ +title @TITLE@ +linux /boot/bzImage +options console=ttyS0 console=hvc0 loglevel=4 +linux-appendroot true diff --git a/external.mk b/external.mk index 071cfee47..5609bd270 100644 --- a/external.mk +++ b/external.mk @@ -25,6 +25,22 @@ FRR_POST_BUILD_HOOKS += FRR_POST_BUILD_HOOK # NETSNMP_CONF_OPTS += --enable-read-only +# Some of these assumes the presence of systemd, so skip them. +LVM2_CONF_OPTS += --disable-udev_rules + +# +# The multipath-tools package, which we need for kpartx, installs udev +# rules that assumes the presence of systemd. There is no option to +# skip the install, but we can pick the destination. So place them in +# the root and then remove them in the post-install hook. +MULTIPATH_TOOLS_OPTS += udevrulesdir="/.mpath-trash" + +define MPATH_POST_INSTALL_CLEANUP + rm -rf $(TARGET_DIR)/.mpath-trash +endef + +MULTIPATH_TOOLS_POST_INSTALL_TARGET_HOOKS += MPATH_POST_INSTALL_CLEANUP + # # External pre-built toolchains do not carry their own license. # diff --git a/package/factory/factory.mk b/package/factory/factory.mk index 1d76d3468..bc2b6a683 100644 --- a/package/factory/factory.mk +++ b/package/factory/factory.mk @@ -11,12 +11,11 @@ FACTORY_SITE_METHOD = local FACTORY_SITE = $(BR2_EXTERNAL_INFIX_PATH)/src/factory FACTORY_REDISTRIBUTE = NO -define FACTORY_CONF_ENV -CFLAGS="$(INFIX_CFLAGS)" -endef +FACTORY_RESETME_PATH=$(if $(IX_IMAGE_DDI),/boot/efi/infix/.factory-reset,/mnt/cfg/infix.reset) define FACTORY_BUILD_CMDS $(TARGET_MAKE_ENV) $(TARGET_CONFIGURE_OPTS) $(MAKE) -C $(@D) \ + CFLAGS="$(INFIX_CFLAGS) -DRESETME_PATH=$(FACTORY_RESETME_PATH)" \ LDLIBS="$(TARGET_LDFLAGS)" endef diff --git a/src/factory/factory.c b/src/factory/factory.c index 33aca3eb3..4259f1cb8 100644 --- a/src/factory/factory.c +++ b/src/factory/factory.c @@ -10,7 +10,13 @@ #include #include -#define RESETME "/mnt/cfg/infix/.reset" +#define stringify(_x) #_x +#define xstringify(_x) stringify(_x) + +#ifndef RESETME_PATH +#error "RESETME_PATH must be specified" +#endif +#define RESETME xstringify(RESETME_PATH) #define BOARDCHECK "/usr/libexec/infix/check-factory" From 500a1fea69b42df6469e7e3c706de77d90f783db Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Thu, 18 Dec 2025 00:18:42 +0000 Subject: [PATCH 10/19] image-ddi-disk: Full system image --- board/common/Config.in | 1 + board/common/image/image-ddi-disk/Config.in | 8 + board/common/image/image-ddi-disk/generate.sh | 69 ++++++ .../image/image-ddi-disk/image-ddi-disk.mk | 9 + board/common/rootfs/etc/partition-uuid | 3 + utils/lvm-mkinternal | 210 ++++++++++++++++++ 6 files changed, 300 insertions(+) create mode 100644 board/common/image/image-ddi-disk/Config.in create mode 100755 board/common/image/image-ddi-disk/generate.sh create mode 100644 board/common/image/image-ddi-disk/image-ddi-disk.mk create mode 100755 utils/lvm-mkinternal diff --git a/board/common/Config.in b/board/common/Config.in index e82b5fd45..3f28073ca 100644 --- a/board/common/Config.in +++ b/board/common/Config.in @@ -3,6 +3,7 @@ menu "Images" comment "DDI Based" source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-ddi/Config.in" +source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-ddi-disk/Config.in" comment "ITB Based" source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-itb-rootfs/Config.in" diff --git a/board/common/image/image-ddi-disk/Config.in b/board/common/image/image-ddi-disk/Config.in new file mode 100644 index 000000000..7b1f60b49 --- /dev/null +++ b/board/common/image/image-ddi-disk/Config.in @@ -0,0 +1,8 @@ +menuconfig IX_IMAGE_DDI_DISK + bool "LVM2 based provisioning disk (DDI)" + depends on IX_IMAGE_DDI + select IX_IMAGE_BAREBOX_ESP + help + Compose a full disk image consisting of an EFI System + Partition (ESP) containing Barebox, and an LVM PV in which + Infix is installed as an LV. diff --git a/board/common/image/image-ddi-disk/generate.sh b/board/common/image/image-ddi-disk/generate.sh new file mode 100755 index 000000000..a4e5966c1 --- /dev/null +++ b/board/common/image/image-ddi-disk/generate.sh @@ -0,0 +1,69 @@ +#!/bin/sh + +set -e + + +mkdir -p "${WORKDIR}"/root +rm -rf "${WORKDIR}"/tmp +mkdir -p "${WORKDIR}"/tmp + +"${BR2_EXTERNAL_INFIX_PATH}"/utils/lvm-mkinternal \ + primary "${BINARIES_DIR}"/"${ARTIFACT}".raw >"${WORKDIR}"/internal.lvm + +. $BR2_EXTERNAL_INFIX_PATH/board/common/rootfs/etc/partition-uuid +[ -n "${BAREBOX_STATE_UUID}" ] +[ -n "${ESP_UUID}" ] +[ -n "${ESP_BACKUP_UUID}" ] + +cat <"${WORKDIR}"/genimage.cfg + +image ${ARTIFACT}.disk { + hdimage { + partition-table-type = "gpt" + } + + partition esp { + partition-type-uuid = "esp" + partition-uuid = "$ESP_UUID" + image = "$BINARIES_DIR/barebox-esp.vfat" + } + + partition esp-backup { + partition-type-uuid = "esp" + partition-uuid = "$ESP_BACKUP_UUID" + image = "$BINARIES_DIR/barebox-esp.vfat" + } + + partition barebox-state { + partition-type-uuid = "barebox-state" + partition-uuid = "$BAREBOX_STATE_UUID" + size = 2048 + } + + partition internal { + growfs = "true" + partition-type-uuid = "lvm" + image = "internal.lvm" + } +} + +image ${ARTIFACT}.qcow2 { + qemu { + format = "qcow2" + } + + partition disk { + image = "${ARTIFACT}.disk" + } +} + +# Silence genimage warnings +config {} +EOF + +genimage \ + --tmppath "${WORKDIR}"/tmp \ + --rootpath "${WORKDIR}"/root \ + --inputpath "${WORKDIR}" \ + --outputpath "${BINARIES_DIR}" \ + --config "${WORKDIR}"/genimage.cfg diff --git a/board/common/image/image-ddi-disk/image-ddi-disk.mk b/board/common/image/image-ddi-disk/image-ddi-disk.mk new file mode 100644 index 000000000..e666e0238 --- /dev/null +++ b/board/common/image/image-ddi-disk/image-ddi-disk.mk @@ -0,0 +1,9 @@ +################################################################################ +# +# image-ddi-disk +# +################################################################################ + +IMAGE_DDI_DISK_DEPENDENCIES := image-barebox-esp image-ddi + +$(eval $(ix-image)) diff --git a/board/common/rootfs/etc/partition-uuid b/board/common/rootfs/etc/partition-uuid index c7e162be7..92be42fef 100644 --- a/board/common/rootfs/etc/partition-uuid +++ b/board/common/rootfs/etc/partition-uuid @@ -1,3 +1,6 @@ AUX_UUID="78460f84-de84-4fe7-89bd-4c1f433b2230" +BAREBOX_STATE_UUID="80ca5554-c020-11f1-ba4d-b3c49d5dbf47" +ESP_UUID="82bb760e-c08e-11f1-a791-7bd312fe0c17" +ESP_BACKUP_UUID="835b97d8-c08e-11f1-a2f7-73ad4bbf39e9" PRIMARY_UUID="107ae911-a97b-4380-975c-7ce1a2dde1e0" SECONDARY_UUID="352bd9b2-2ca9-44e2-bdc7-edbc87ba1e02" diff --git a/utils/lvm-mkinternal b/utils/lvm-mkinternal new file mode 100755 index 000000000..14fd2d812 --- /dev/null +++ b/utils/lvm-mkinternal @@ -0,0 +1,210 @@ +#!/usr/bin/env python3 + +import os +import random +import struct + +class ID: + def __init__(self): + charset = "abcdefghijklmnopqrstuvwxyz" + charset += "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + charset += "0123456789" + + b = random.choices(charset, k=32) + self.bytes = "".join(b).encode() + + def dashed(self): + return self.bytes[0:6].decode() + \ + "-" + self.bytes[6:10].decode() + \ + "-" + self.bytes[10:14].decode() + \ + "-" + self.bytes[14:18].decode() + \ + "-" + self.bytes[18:22].decode() + \ + "-" + self.bytes[22:26].decode() + \ + "-" + self.bytes[26:32].decode() + +def crc32(data: bytes) -> int: + crc = 0xf597a6cf + + for byte in data: + crc ^= byte + for _ in range(8): + if crc & 1: + crc = (crc >> 1) ^ 0xedb88320 + else: + crc >>= 1 + + return crc & 0xffffffff + +def sendfile(dst, src, count): + while count > 0: + chunk = os.sendfile(dst, src, None, count) + if chunk < 0: + raise OSError("sendfile") + + count -= chunk + +class Linear: + def __init__(self, start, align, path, name=None): + self.id = ID() + self.name = name if name else os.path.basename(path) + self.path = path + self.start = start + self.align = align + self.size = os.path.getsize(path) + self.asize = (self.size + align - 1) & ~(align - 1) + + def sendfile(self, fd): + pad = self.asize - self.size + + with open(self.path, "rb") as f: + sendfile(fd, f.fileno(), self.size) + + if pad: + os.write(fd, b"\0" * pad) + + def meta(self): + return f""" {self.name} {{ + id = "{self.id.dashed()}" + status = ["READ", "WRITE", "VISIBLE"] + segment_count = 1 + + segment1 {{ + start_extent = 0 + extent_count = {self.asize // self.align } + type = "striped" + stripe_count = 1 + stripes = [ + "pv0", {self.start // self.align } + ] + }} + }}""" + +class InternalPV: + def __init__(self, images=[], align=(32 << 10), creation_time=0): + self.pvid = ID() + self.vgid = ID() + self.align = align + self.creation_time = creation_time + + self.lvs = [] + self.dsize = 0 + for img, path in images: + lv = Linear(self.dsize, align, path, img) + self.lvs.append(lv) + self.dsize += lv.asize + + # Reserve `align` bytes for primary metadata area, the size + # used by all LVs, and another `aligned` bytes for the backup + # metadata area + self.size = align + self.dsize + align + + def write(self, dst): + for i in range(4): + dst.write(self._label(i)) + + dst.write(b"\0" * 0x800) + + dst.write(self._metadata(True)) + + for lv in self.lvs: + lv.sendfile(dst.fileno()) + + dst.write(self._metadata(False)) + + def _label(self, sector: int): + headfmt, tailfmt = "<8s Q I", "", 1, + offs, size, + 512, len(txt), crc32(txt), 0, + 0, 0, 0, 0) + b"\0" * padsize + + head = struct.pack(headfmt, crc32(tail)) + + return head + tail + txt + bytes([0] * txtpadsize) + + def _meta_txt(self): + return f"""internal {{ + id = "{self.vgid.dashed()}" + seqno = 1 + status = ["RESIZEABLE", "READ", "WRITE"] + extent_size = {self.align >> 9} + max_pv = 0 + max_lv = 0 + metadata_copies = 2 + + physical_volumes {{ + pv0 {{ + id = "{self.pvid.dashed()}" + dev = "/dev/internal" + status = ["ALLOCATABLE"] + dev_size = {self.size >> 9} + pe_start = {self.align >> 9} + pe_count = {self.dsize // self.align } + }} + }} + + logical_volumes {{ +{"\n".join([lv.meta() for lv in self.lvs])} + }} +}} + +contents = "Text Format Volume Group" +version = 1 +description = "Internal image storage" +creation_host = "infix-build-system" +creation_time = {self.creation_time} +""".encode() + + +def main(): + import argparse + import datetime + import sys + + parser = argparse.ArgumentParser(description="Create LVM PV containing a set of images") + parser.add_argument("-T", "--creation-time", metavar="TIMESTAMP", + default=int(datetime.datetime.now().timestamp())) + parser.add_argument("images", metavar="IMAGE", nargs="*") + args = parser.parse_args() + args.images = list(zip(args.images[::2], args.images[1::2])) + + random.seed(args.creation_time) + + pv = InternalPV(args.images, creation_time=args.creation_time) + pv.write(sys.stdout.buffer) + +if __name__ == "__main__": + main() From 552d447adef161312a21704a83a84a3101ff0c97 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 7 Oct 2026 14:59:44 +0000 Subject: [PATCH 11/19] image-ddi-rauc: Add RAUC update bundles for DDI images --- board/common/Config.in | 1 + board/common/image/image-ddi-rauc/Config.in | 27 ++++++++++++ board/common/image/image-ddi-rauc/generate.sh | 34 ++++++++++++++ board/common/image/image-ddi-rauc/hooks.sh | 44 +++++++++++++++++++ .../image/image-ddi-rauc/image-itb-rauc.mk | 10 +++++ 5 files changed, 116 insertions(+) create mode 100644 board/common/image/image-ddi-rauc/Config.in create mode 100755 board/common/image/image-ddi-rauc/generate.sh create mode 100755 board/common/image/image-ddi-rauc/hooks.sh create mode 100644 board/common/image/image-ddi-rauc/image-itb-rauc.mk diff --git a/board/common/Config.in b/board/common/Config.in index 3f28073ca..1ccee1a14 100644 --- a/board/common/Config.in +++ b/board/common/Config.in @@ -4,6 +4,7 @@ menu "Images" comment "DDI Based" source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-ddi/Config.in" source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-ddi-disk/Config.in" +source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-ddi-rauc/Config.in" comment "ITB Based" source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-itb-rootfs/Config.in" diff --git a/board/common/image/image-ddi-rauc/Config.in b/board/common/image/image-ddi-rauc/Config.in new file mode 100644 index 000000000..6389bf363 --- /dev/null +++ b/board/common/image/image-ddi-rauc/Config.in @@ -0,0 +1,27 @@ +menuconfig IX_IMAGE_DDI_RAUC + bool "RAUC upgrade bundle (DDI)" + select IX_IMAGE_DDI + select BR2_PACKAGE_HOST_RAUC + help + Create RAUC upgrade bundle, for targets using DDI images, + that can be used to upgrade a running system to this version + of Infix. + +config IX_IMAGE_DDI_RAUC_KEY + string "signing key" + depends on IX_IMAGE_DDI_RAUC + default "${BR2_EXTERNAL_INFIX_PATH}/board/common/signing-keys/development/infix.key" + help + Path to the private key, in PKCS#8 format, used to sign + the RAUC bundle; or a PKCS#11 URI. + +config IX_IMAGE_DDI_RAUC_CERT + string "signing certificate" + depends on IX_IMAGE_DDI_RAUC + default "${BR2_EXTERNAL_INFIX_PATH}/board/common/signing-keys/development/infix.crt" + help + Path to the X509 certificate which will be associated with + the bundle signature. + + NOTE: This cert MUST be included in the trust store of the + system on which this bundle is to be installed. diff --git a/board/common/image/image-ddi-rauc/generate.sh b/board/common/image/image-ddi-rauc/generate.sh new file mode 100755 index 000000000..1caf05118 --- /dev/null +++ b/board/common/image/image-ddi-rauc/generate.sh @@ -0,0 +1,34 @@ +#!/bin/sh + +set -e + +ddi="${BINARIES_DIR}"/"${ARTIFACT}.raw" +pkg="${BINARIES_DIR}"/"${ARTIFACT}.pkg" + +cp -f "${PKGDIR}"/hooks.sh "${WORKDIR}"/hooks.sh + +# RAUC <= 1.15 uses the file extension to find a suitable install +# handler, which must be .img in order to select the "raw" type. +cp -f "${ddi}" "${WORKDIR}"/ +ln -sf "${ARTIFACT}.raw" "${WORKDIR}"/ddi.img + +cat >"${WORKDIR}"/manifest.raucm <&2 +} + +die() +{ + echo "ERROR: $*" >&2 + exit 1 +} + +case "$1" in + slot-pre-install) + [ "$RAUC_SLOT_CLASS" = "rootfs" ] || break + + dst=/dev/mapper/"$RAUC_SLOT_BOOTNAME" \ + || die "$RAUC_SLOT_BOOTNAME is not available" + dstsize=$(lvs --reportformat=json --units b "$dst" \ + | jq -r .report[0].lv[0].lv_size \ + | tr -d B) + + src="$RAUC_BUNDLE_MOUNT_POINT"/ddi.img + [ -f "$src" ] \ + || die "No DDI in bundle" + srcsize=$(stat -L -c %s "$src") + + kpartx -d "$dst" \ + || die "Unable to tear down partitions" + + [ "$dstsize" -eq "$srcsize" ] || { + lvm lvresize --yes --size ${newsize}B "$dst" \ + || die "Unable to resize $RAUC_SLOT_BOOTNAME to ${srcsize}B" + } + + note "$RAUC_SLOT_BOOTNAME is ready to accept update" + ;; + *) + exit 1 + ;; +esac diff --git a/board/common/image/image-ddi-rauc/image-itb-rauc.mk b/board/common/image/image-ddi-rauc/image-itb-rauc.mk new file mode 100644 index 000000000..942b050b6 --- /dev/null +++ b/board/common/image/image-ddi-rauc/image-itb-rauc.mk @@ -0,0 +1,10 @@ +################################################################################ +# +# image-ddi-rauc +# +################################################################################ + +IMAGE_DDI_RAUC_DEPENDENCIES := host-rauc image-ddi +IMAGE_DDI_RAUC_CONFIG_VARS := KEY CERT + +$(eval $(ix-image)) From fd10de85189a37932da8aff073c2b3d649d35f31 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Thu, 26 Feb 2026 14:25:30 +0000 Subject: [PATCH 12/19] image-lvm-stub: Base image for use with 'make run' --- board/common/Config.in | 1 + board/common/image/image-lvm-stub/Config.in | 9 +++ board/common/image/image-lvm-stub/generate.sh | 58 +++++++++++++++++++ .../image/image-lvm-stub/image-lvm-stub.mk | 9 +++ 4 files changed, 77 insertions(+) create mode 100644 board/common/image/image-lvm-stub/Config.in create mode 100755 board/common/image/image-lvm-stub/generate.sh create mode 100644 board/common/image/image-lvm-stub/image-lvm-stub.mk diff --git a/board/common/Config.in b/board/common/Config.in index 1ccee1a14..60a3b92d1 100644 --- a/board/common/Config.in +++ b/board/common/Config.in @@ -18,6 +18,7 @@ source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-itb-dl-release/Config. comment "General" source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-barebox-esp/Config.in" +source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-lvm-stub/Config.in" source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-readme/Config.in" endmenu diff --git a/board/common/image/image-lvm-stub/Config.in b/board/common/image/image-lvm-stub/Config.in new file mode 100644 index 000000000..48a31f635 --- /dev/null +++ b/board/common/image/image-lvm-stub/Config.in @@ -0,0 +1,9 @@ +config IX_IMAGE_LVM_STUB + bool "LVM2 stub image" + default y if IX_IMAGE_DDI + depends on IX_IMAGE_BAREBOX_ESP + help + A disk image with Barebox and an empty LVM2 physical volume + attached to the internal volume group. This is useful as a + base image for persistent storage in QEMU emulation + scenarios where the OS is supplied in a separate image. diff --git a/board/common/image/image-lvm-stub/generate.sh b/board/common/image/image-lvm-stub/generate.sh new file mode 100755 index 000000000..5f54f60f8 --- /dev/null +++ b/board/common/image/image-lvm-stub/generate.sh @@ -0,0 +1,58 @@ +#!/bin/sh + +set -e + + +mkdir -p "${WORKDIR}"/root +rm -rf "${WORKDIR}"/tmp +mkdir -p "${WORKDIR}"/tmp + +"${BR2_EXTERNAL_INFIX_PATH}"/utils/lvm-mkinternal >"${WORKDIR}"/stub.lvm + +. $BR2_EXTERNAL_INFIX_PATH/board/common/rootfs/etc/partition-uuid +[ -n "${BAREBOX_STATE_UUID}" ] +[ -n "${ESP_UUID}" ] +[ -n "${ESP_BACKUP_UUID}" ] + +cat <"${WORKDIR}"/genimage.cfg +image lvm-stub.disk { + hdimage { + partition-table-type = "gpt" + } + + partition esp { + partition-type-uuid = "esp" + partition-uuid = "$ESP_UUID" + image = "$BINARIES_DIR/barebox-esp.vfat" + } + + partition esp-backup { + partition-type-uuid = "esp" + partition-uuid = "$ESP_BACKUP_UUID" + image = "$BINARIES_DIR/barebox-esp.vfat" + } + + partition barebox-state { + partition-type-uuid = "barebox-state" + partition-uuid = "$BAREBOX_STATE_UUID" + size = 2048 + } + + partition internal { + growfs = "true" + image = "stub.lvm" + partition-type-uuid = "lvm" + size = 2M + } +} + +# Silence genimage warnings +config {} +EOF + +genimage \ + --tmppath "${WORKDIR}"/tmp \ + --rootpath "${WORKDIR}"/root \ + --inputpath "${WORKDIR}" \ + --outputpath "${BINARIES_DIR}" \ + --config "${WORKDIR}"/genimage.cfg diff --git a/board/common/image/image-lvm-stub/image-lvm-stub.mk b/board/common/image/image-lvm-stub/image-lvm-stub.mk new file mode 100644 index 000000000..b12dfa90e --- /dev/null +++ b/board/common/image/image-lvm-stub/image-lvm-stub.mk @@ -0,0 +1,9 @@ +################################################################################ +# +# image-lvm-stub +# +################################################################################ + +IMAGE_LVM_STUB_DEPENDENCIES := image-barebox-esp + +$(eval $(ix-image)) From 25d2e45b8f1f13264915c3c38b9e0df9741e90f4 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 29 Sep 2026 11:21:54 +0000 Subject: [PATCH 13/19] board/common: Support signing the Linux kernel --- board/common/Config.in | 22 ++++++++++++++++++++++ board/common/common.mk | 12 ++++++++++++ 2 files changed, 34 insertions(+) diff --git a/board/common/Config.in b/board/common/Config.in index 60a3b92d1..48e0181fa 100644 --- a/board/common/Config.in +++ b/board/common/Config.in @@ -23,6 +23,28 @@ source "$BR2_EXTERNAL_INFIX_PATH/board/common/image/image-readme/Config.in" endmenu + +menuconfig IX_KERNEL_SIGN + bool "Sign Linux Kernel" + default y if BR2_x86_64 + +config IX_KERNEL_SIGN_KEY + string "signing key" + depends on IX_KERNEL_SIGN + default "${BR2_EXTERNAL_INFIX_PATH}/board/common/signing-keys/development/infix.key" + help + Path to the private RSA key, in PKCS#8 format, used to sign + the kernel image. + +config IX_KERNEL_SIGN_CERT + string "signing cert" + depends on IX_KERNEL_SIGN + default "${BR2_EXTERNAL_INFIX_PATH}/board/common/signing-keys/development/infix.crt" + help + Path to the X509 certificate whose associated public key can + validate the authenticity of the generated signature. + + config IX_QEMU_SCRIPTS bool "QEMU scripts" default y diff --git a/board/common/common.mk b/board/common/common.mk index a570f09d4..af6b73f74 100644 --- a/board/common/common.mk +++ b/board/common/common.mk @@ -2,6 +2,18 @@ include $(BR2_EXTERNAL_INFIX_PATH)/board/common/barebox/barebox.mk include $(BR2_EXTERNAL_INFIX_PATH)/board/common/image/image.mk include $(BR2_EXTERNAL_INFIX_PATH)/board/common/qemu/qemu.mk +ifeq ($(IX_KERNEL_SIGN),y) +define LINUX_POST_BUILD_SIGN + @$(call IXMSG,"Signing $(notdir $(LINUX_IMAGE_PATH))") + sbsign \ + --key "$(IX_KERNEL_SIGN_KEY)" \ + --cert "$(IX_KERNEL_SIGN_CERT)" \ + --output "$(LINUX_IMAGE_PATH)" \ + "$(LINUX_IMAGE_PATH)" +endef +LINUX_POST_BUILD_HOOKS += LINUX_POST_BUILD_SIGN +endif + ifeq ($(IX_TRUSTED_KEYS),y) include $(BR2_EXTERNAL_INFIX_PATH)/board/common/uboot/uboot.mk From 3e20b72ea26faaf63848adcfa37ac266a9cd7e00 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Thu, 26 Feb 2026 12:32:46 +0000 Subject: [PATCH 14/19] board/common: ddi: Add helper tool to work with DDIs First user is the initramfs, where it is used to setup the verity device. Later on, it will be extended for use by RAUC during upgrades, to fetch image info in statd, etc. --- board/common/rootfs/usr/bin/ddi | 343 ++++++++++++++++++++++++++++++++ 1 file changed, 343 insertions(+) create mode 100755 board/common/rootfs/usr/bin/ddi diff --git a/board/common/rootfs/usr/bin/ddi b/board/common/rootfs/usr/bin/ddi new file mode 100755 index 000000000..39f795e07 --- /dev/null +++ b/board/common/rootfs/usr/bin/ddi @@ -0,0 +1,343 @@ +#!/bin/sh + +set -e + +capath=/etc/rauc/keys + +usage() +{ + local _ddi="$(basename $0)" + + cat <] [] + +Discoverable Disk Image manipulation + + Options: + + -A + Override the system architecture. + + -C + Source trusted signing keys from the supplied directory. + Default: $capath + + -h + Show this message and exit. + + Commands: + + inspect + + uuid + Print GPT disk identifier + + part [ is one of: + root The first root partition + + open [] + + Create a device mapping for the specified partition, called + . + + Options: + + -v + Use the associated dm-verity hash tree, whose root hash is + available in the associated signature partition. + + -V + Use the associated dm-verity hash tree, along with an + externally validated root hash. + + verify [] + + Verify that the specified partition exists and, optionally, + is accompanied by a trusted dm-verity hash tree. + + Options: + + -v + Require the presence of an associated dm-verity hash tree, + whose root hash is available in the associated signature + partition. + + -V + Require the presence of an associated dm-verity hash tree + whose root hash matches the provided one. + + Examples: + + Map and mount the verity protected SquashFS root partition from an + image: + $_ddi infix-x86_64.raw part root open -v infix + mount -t squashfs /dev/mapper/infix /opt + +EOF +} + +X86_64_CODE_ROOT=0x8304 +X86_64_CODE_ROOT_VERITY=0x830c +X86_64_CODE_ROOT_VERITY_SIG=0x8370 + +die() +{ + echo "ERROR: $*" >&2 + exit 1 +} + +ok() +{ + echo "$*" >&2 +} + +setup_arch() +{ + case "$1" in + x86_64) + CODE_ROOT=$X86_64_CODE_ROOT + CODE_ROOT_VERITY=$X86_64_CODE_ROOT_VERITY + CODE_ROOT_VERITY_SIG=$X86_64_CODE_ROOT_VERITY_SIG + ;; + *) + die "Unsupported architecture" + esac +} + +setup_work() +{ + local _umask + + _umask=$(umask) + umask 077 + workdir=$(mktemp -d) + umask $_umask + + trap 'rm -rf "$workdir"' EXIT INT TERM +} + +ddi_to_block() +{ + local _kind _lo + + _kind=$(stat -Lc %F "$1") + case "$_kind" in + "block special file") + echo "$1" + return + ;; + "regular file") + _lo=$(losetup -f || die "Found no free loop device for $1") + losetup -P "$_lo" "$1" || die "Failed to setup loop device of $1" + echo "$_lo" + return + ;; + esac + + die "$1 is a $_kind, only regular and block devices are supported" +} + +ddi_get_part_info() +{ + local _code _item + + case "$2" in + root) _code="$CODE_ROOT";; + root-verity) _code="$CODE_ROOT_VERITY";; + root-verity-sig) _code="$CODE_ROOT_VERITY_SIG";; + *) die "Unknown partition type \"$1\"" ;; + esac + _code=$(printf "%04X" "$_code") + _item=${3:-exists} + + sgdisk "$1" -p | awk -v code="$_code" -v item="$_item" ' + /^[ ]*[0-9]+/ { + if ($6 != code) + next; + + if (item == "exists") exit(0); + if (item == "index") { print($1); exit(0); } + + exit(1); + } + ' +} + +ddi_get_part_dev() +{ + local _dev _index _lvuuid _subsys + + _index=$(ddi_get_part_info "$1" "$2" index \ + || die "No $2 partition found on $1") + + _subsys=$(dmsetup info -c --noheadings -o subsystem "$1" 2>/dev/null || true) + if [ "$_subsys" = "LVM" ]; then + _lvuuid=$(dmsetup info -c --noheadings -o uuid "$1") + _dev=/dev/mapper/$(dmsetup info -c --noheadings -o name -u "part${_index}-$_lvuuid") + else + _dev=$(basename "$1") + _dev=$(find /sys/block/"$_dev"/ -mindepth 1 -maxdepth 1 -type d \ + -name "${_dev}$_index" -o -name "${_dev}p$_index") + _dev=/dev/$(basename "$_dev") + fi + + [ -b "$_dev" ] || die "Failed to locate $2 partition in $1" + + echo "$_dev" +} + +ddi_get_root_hash() +{ + local _subj + + command -v openssl >/dev/null || die "Missing dependency: openssl" + command -v jq >/dev/null || die "Missing dependency: jq" + + jq -r .rootHash "$1" | tr -d '\n' >"$workdir"/hash \ + || die "Invalid signature: rootHash is missing" + + jq -r .signature "$1" | base64 -d >"$workdir"/sig \ + || die "Invalid signature: signature data is missing" + + openssl cms -verify -verify_retcode -CApath "$capath" \ + -content "$workdir"/hash \ + -in "$workdir"/sig -inform DER \ + -certsout "$workdir"/certs >/dev/null 2>&1 \ + || die "Signature verification failed" + + _subj=$(openssl x509 -noout -subject <"$workdir"/certs | sed -e 's/^subject=//') + ok "Trusting root hash signature from $_subj" + + cat "$workdir"/hash +} + +ddipart_get_verity() +{ + local _hash + + while getopts "vV:" opt; do + case "$opt" in + V) + vkind="${kind}-verity" + vhash="$OPTARG" + ;; + v) + vkind="${kind}-verity" + ;; + esac + done + + [ "$vkind" ] || return + + vpart=$(ddi_get_part_dev "$img" "$vkind") + + [ "$vhash" ] && return + + vskind="${vkind}-sig" + vspart=$(ddi_get_part_dev "$img" "$vskind") + vhash=$(ddi_get_root_hash "$vspart") +} + +ddipart_open() +{ + [ "$vpart" ] || die "$kind partition is already accessible at $part" + + shift $((OPTIND - 1)) + + veritysetup open "$part" "$1" "$vpart" "$vhash" +} + +ddipart_verify() +{ + [ "$vpart" ] || return + + veritysetup verify "$part" "$vpart" "$vhash" + ok "Image in $img has $kind partition with valid dm-verity hash tree" +} + +ddipart() +{ + local _cmd + + kind="$1" + shift + + ddi_get_part_info "$img" "$kind" \ + || die "No $kind partition found on $img" + + _cmd="$1" + shift + + case "$_cmd" in + open|verify) + img=$(ddi_to_block $img) + part=$(ddi_get_part_dev "$img" "$kind") + ddipart_get_verity "$@" + + ddipart_$_cmd "$@" + ;; + *) + die "Unknown partition command \"$_cmd\"" + ;; + esac +} + +ddiinspect() +{ + case "$1" in + uuid) + sgdisk "$img" -p | awk ' + BEGIN { err = 1; } END { exit(err); } + /^Creating new GPT/ { exit; } + + /^Disk identifier \(GUID\): / { + print($4); err = 0; exit; + }' + ;; + *) + die "Unknown property: $1" + ;; + esac +} + +_arch=$(uname -m) + +while getopts "A:C:h" opt; do + case ${opt} in + A) + _arch="$OPTARG" + ;; + C) + capath="$OPTARG" + ;; + h) + usage && exit 0 + ;; + esac +done +shift $((OPTIND - 1)) + +if [ $# -lt 2 ]; then + usage && exit 1 +fi + +img="$1" +cmd="$2" +shift 2 + +setup_arch "$_arch" +setup_work + +case "$cmd" in + inspect) + ddiinspect "$@" + ;; + part) + ddipart "$@" + ;; + *) + echo "Unknown command \"$cmd\"" >2 + exit 1 + ;; +esac From 5aa6f5de1f6c67f1df56c1608f5993b8f51d923a Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 4 Feb 2026 18:37:20 +0000 Subject: [PATCH 15/19] initramfs: Build initramfs capable of booting DDI images --- board/common/common.mk | 1 + board/common/image/image-ddi/Config.in | 1 + board/common/initramfs/dracut.conf | 104 +++++++ board/common/initramfs/initramfs.mk | 38 +++ .../modules.d/01infix-lib64/module-setup.sh | 15 + .../initramfs/modules.d/10infix-init/init | 276 ++++++++++++++++++ .../modules.d/10infix-init/module-setup.sh | 20 ++ board/x86_64/linux_defconfig | 5 +- 8 files changed, 459 insertions(+), 1 deletion(-) create mode 100644 board/common/initramfs/dracut.conf create mode 100644 board/common/initramfs/initramfs.mk create mode 100755 board/common/initramfs/modules.d/01infix-lib64/module-setup.sh create mode 100755 board/common/initramfs/modules.d/10infix-init/init create mode 100755 board/common/initramfs/modules.d/10infix-init/module-setup.sh diff --git a/board/common/common.mk b/board/common/common.mk index af6b73f74..fd31bfb01 100644 --- a/board/common/common.mk +++ b/board/common/common.mk @@ -1,5 +1,6 @@ include $(BR2_EXTERNAL_INFIX_PATH)/board/common/barebox/barebox.mk include $(BR2_EXTERNAL_INFIX_PATH)/board/common/image/image.mk +include $(BR2_EXTERNAL_INFIX_PATH)/board/common/initramfs/initramfs.mk include $(BR2_EXTERNAL_INFIX_PATH)/board/common/qemu/qemu.mk ifeq ($(IX_KERNEL_SIGN),y) diff --git a/board/common/image/image-ddi/Config.in b/board/common/image/image-ddi/Config.in index 75b6a1c94..42a80abe6 100644 --- a/board/common/image/image-ddi/Config.in +++ b/board/common/image/image-ddi/Config.in @@ -9,6 +9,7 @@ menuconfig IX_IMAGE_DDI select BR2_PACKAGE_MULTIPATH_TOOLS select BR2_PACKAGE_UTIL_LINUX_BINARIES select BR2_PACKAGE_HOST_GENIMAGE + select BR2_PACKAGE_HOST_DRACUT help Create a Discoverable Disk Image containing: - SquashFS of the root filesystem diff --git a/board/common/initramfs/dracut.conf b/board/common/initramfs/dracut.conf new file mode 100644 index 000000000..126b38d55 --- /dev/null +++ b/board/common/initramfs/dracut.conf @@ -0,0 +1,104 @@ +#Simple dracut config for a system without systemd + +#Dracut configuration + +show_modules=yes +i18n_install_all=no +lvmconf=no +mdadmconf=no +early_microcode=no +hostonly=no +hostonly_cmdline=no +use_fstab=no +kernel_cmdline="rd.break=initqueue" +do_strip=no + +# Dracut modules needed +add_dracutmodules+=" \ +busybox \ +infix-init \ +infix-lib64 +" + +# Modules to ignore +omit_dracutmodules+=" \ +base \ +bash \ +biosdevname \ +btrfs \ +bluetooth \ +caps \ +cifs \ +connman \ +crypt \ +crypt-gpg \ +dash \ +dbus-broker \ +dbus-daemon \ +dm \ +dmraid \ +dmsquash-live-ntfs \ +dracut-systemd \ +fcoe \ +fcoe-uefi \ +fs-lib \ +iscsi \ +i18n \ +kernel-modules \ +kernel-modules-extra \ +lvmmerge \ +lvm \ +lunmask \ +mdraid \ +memstrack \ +mksh \ +modsign \ +mount-root \ +multipath \ +nbd \ +network-legacy \ +network-wicked \ +nfs \ +nvdimm \ +nvmf \ +parse-lunmask \ +pcsc \ +qemu \ +qemu-net \ +resume \ +rootfs-block \ +rngd \ +shutdown \ +systemd \ +systemd-udevd \ +systemd-coredump \ +systemd-ask-password \ +systemd-timedated \ +systemd-rfkill \ +systemd-resolved \ +systemd-hostnamed \ +systemd-initrd \ +systemd-integritysetup \ +systemd-journald \ +systemd-ldconfig \ +systemd-networkd \ +systemd-timesyncd \ +systemd-veritysetup \ +systemd-modules-load \ +systemd-pcrphase \ +systemd-portabled \ +systemd-pstore \ +systemd-sysctl \ +systemd-repart \ +systemd-sysext \ +systemd-sysusers \ +systemd-tmpfiles \ +squash \ +ssh-client \ +stratis \ +tpm2-tss \ +udev-rules \ +url-lib \ +usrmount \ +virtiofs \ +" diff --git a/board/common/initramfs/initramfs.mk b/board/common/initramfs/initramfs.mk new file mode 100644 index 000000000..1a033464a --- /dev/null +++ b/board/common/initramfs/initramfs.mk @@ -0,0 +1,38 @@ +ifeq ($(IX_IMAGE_DDI),y) + +IX_INITRAMFS_DIR := $(BR2_EXTERNAL_INFIX_PATH)/board/common/initramfs +IX_INITRAMFS_BUILD := $(BUILD_DIR)/infix-initramfs +IX_INITRAMFS_DEPS := \ + host-dracut \ + host-fakeroot \ + busybox \ + cryptsetup \ + gptfdisk \ + jq \ + lvm2 \ + multipath-tools \ + util-linux + +define LINUX_PRE_BUILD_INITRAMFS + @$(call IXMSG,"Creating Infix initramfs") + mkdir -p $(IX_INITRAMFS_BUILD) + cp -a $(IX_INITRAMFS_DIR)/modules.d/* $(HOST_DIR)/lib/dracut/modules.d/ + $(HOST_DIR)/bin/fakeroot $(HOST_DIR)/bin/dracut \ + -c $(IX_INITRAMFS_DIR)/dracut.conf \ + --kver $(LINUX_VERSION) --no-kernel \ + --sysroot $(TARGET_DIR) \ + --tmpdir $(IX_INITRAMFS_BUILD) \ + -M \ + --force \ + --no-compress \ + $(@D)/infix-initramfs.cpio +endef +LINUX_PRE_BUILD_HOOKS += LINUX_PRE_BUILD_INITRAMFS + +# We are late to the party here, buildroot/linux/linux.mk has already +# been sourced, so in addition to the dependency list, we also need to +# "manually" extend the deps for the kernel's configure target. +LINUX_DEPENDENCIES += $(IX_INITRAMFS_DEPS) +$(LINUX_TARGET_CONFIGURE): | $(IX_INITRAMFS_DEPS) + +endif diff --git a/board/common/initramfs/modules.d/01infix-lib64/module-setup.sh b/board/common/initramfs/modules.d/01infix-lib64/module-setup.sh new file mode 100755 index 000000000..790e9d6b9 --- /dev/null +++ b/board/common/initramfs/modules.d/01infix-lib64/module-setup.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +check() { + return 0 +} + +depends() { + return 0 +} + +install() { + # Not sure if this should be handled by buildroot's merged-usr + # module. Fix it here for now. + ln -s "lib" "${initdir?}/lib64" +} diff --git a/board/common/initramfs/modules.d/10infix-init/init b/board/common/initramfs/modules.d/10infix-init/init new file mode 100755 index 000000000..56a1746a8 --- /dev/null +++ b/board/common/initramfs/modules.d/10infix-init/init @@ -0,0 +1,276 @@ +#!/bin/sh + +echo -e "\e[31m●\e[0m \e[33m●\e[0m \e[32m●\e[0m Infix Initramfs — @VERSION@" + +set -e + +getkopt() +{ + grep -q -E "(^| )$1( |$)" /proc/cmdline +} + +getkparam() +{ + sed -n "s/.*\<$1=\([^ ]*\).*/\1/p" /proc/cmdline +} + +debug_shell() +{ + [ "$(getkparam rd.shell)" = "$1" ] || return 0 + + echo "Starting debug shell at $1" + exec sh -i +} + +die() +{ + step="${*:-$step}" + + echo -e "\r\e[0K[\e[31mFAIL\e[0m] $step" + echo "<0>ix-initramfs: ERROR: $step" >/dev/kmsg + debug_shell error + exit 1 +} + +warn() +{ + step="${*:-$step}" + + echo -e "\r\e[0K[\e[33mWARN\e[0m] $step" + echo "<5>ix-initramfs: WARNING: $step" >/dev/kmsg + debug_shell warning +} + +ok() +{ + step="${*:-$step}" + + echo -e "\r\e[0K[\e[32m OK \e[0m] $step" + echo "<5>ix-initramfs: $step" >/dev/kmsg +} + +begin() +{ + step="$*" + echo -ne "[\e[33m ⋯ \e[0m] $step" +} + +debug() +{ + echo "<7>ix-initramfs: $*" >/dev/kmsg +} + +getdisk() +{ + local _disk _uuid _kind + + case "$1" in + PARTUUID=*) + _uuid=$(echo "$1" | sed -e 's/PARTUUID=//' | tr 'A-F' 'a-f') + _disk=/dev/disk/by-partuuid/"$_uuid" + ;; + *) + _disk="$1" + ;; + esac + + _disk=$(realpath "$_disk") + _kind=$(stat -c %F "$_disk" 2>/dev/null || true) + [ "$_kind" = "block special file" ] && echo "$_disk" || true +} + +mountcore() +{ + mount -t proc proc /proc + mount -t sysfs sys /sys + mount -t devtmpfs dev /dev +} + +mountroot() +{ + local _disk=$(getdisk $(getkparam root)) + + [ -z "$_disk" ] \ + && die "Unknown root device \"$(getkparam root)\"" + + mkdir /newroot + mount "$_disk" /newroot || \ + die "Unable to mount rootfs on $_disk" + + ok "Mounted root filesystem" +} + +switchroot() +{ + local innerinit=$(getkparam init) + + begin "Switching to root filesystem" + exec switch_root /newroot ${innerinit:-/sbin/init} + die "Unable to switch to root filesystem" +} + +internal_get_part() +{ + local _i _pv + + for _i in $(seq 50); do + _pv=$(lvm pvs --reportformat json \ + | jq -r '.report[].pv[] | select(.vg_name == "internal") | .pv_name') + if [ "$_pv" ]; then + [ $_i -gt 5 ] && ok + echo "$_pv" + return 0 + fi + + [ $_i -eq 5 ] && begin "Waiting for the internal volume to come online" + sleep .2 + done + + warn "Proceeding without the internal volume" + return 0 +} + +internal_inflate() +{ + local _dev _growfs _index _part + + getkopt rd.nointernal && return 0 + + GPT_PART_GROWFS_BIT=59 + + _part=$(internal_get_part) + [ "$_part" ] || return 0 + + _index=$(cat /sys/class/block/$(basename "$_part")/partition) + [ "$_index" ] || return 1 + + _dev=/dev/$(basename $(realpath /sys/class/block/$(basename "$_part")/../)) + [ -b "$_dev" ] || return 1 + + sgdisk "$_dev" -A "$_index":get:$GPT_PART_GROWFS_BIT \ + | grep -q "$_index:$GPT_PART_GROWFS_BIT:1" \ + || return 0 + + # Make a note to userspace that this is the first time the system + # is booting. + touch /dev/firstboot + + begin "Inflating internal VG in $_part" + + # First, expand the GPT to cover the entire disk and expand the + # LVM partition as much as possible. + if ! sgdisk "$_dev" \ + --disk-guid=random \ + --set-alignment=1 \ + --move-second-header \ + --delete="$_index" \ + --largest-new="$_index" --typecode="$_index":8e00 \ + >/tmp/internal-inflate.log 2>&1; then + warn "Failed to inflate GPT on $_dev" + return 0 + fi + + if ! lvm pvresize "$_part" >>/tmp/internal-inflate.log 2>&1; then + warn "Failed to resize PV $_part" + return 0 + fi + + ok "Inflated internal VG in $_part" +} + +initrd_probe() +{ + getkopt rd.noinitrd && return 0 + [ -f /initrd.image ] || return 0 + + begin "Creating initrd loop device" + + if ! losetup -P -f /initrd.image; then + warn "initrd_probe: losetup failed" + return 0 + fi + + ok "Created initrd loop device" +} + +partuuids_probe() +{ + mkdir -p /dev/disk/by-partuuid + + grep -H PARTUUID= /sys/class/block/*/uevent | \ + sed -E 's|^/sys/class/block/([^/]+)/uevent:PARTUUID=(.*)$|\1 \2|' | \ + while read name uuid; do + ln -s /dev/"$name" /dev/disk/by-partuuid/"$uuid" 2>/dev/null || true + done +} + +lvm_probe() +{ + getkopt rd.nolvm && return 0 + + begin "Activating logical volumes" + + if ! lvm vgchange -ay &>/dev/null; then + warn "lvm_probe: Failed to activate the $_vg VG" + return 0 + fi + + for _lv in $(dmsetup info -c --noheadings -o name); do + sfdisk -J /dev/mapper/"$_lv" >/tmp/lv 2>/dev/null || continue + jq -e '.partitiontable.label == "gpt"' /tmp/lv >/dev/null 2>&1 || continue + + jq -r '.partitiontable | select(.label == "gpt") | + .partitions[] | "\(.uuid | ascii_downcase) \(.node)"' /tmp/lv | \ + while read uuid node; do + ln -s "$node" /dev/disk/by-partuuid/"$uuid" 2>/dev/null || true + done + + kpartx -a -p-part /dev/mapper/"$_lv" || \ + warn "lvm_probe:: Unable to add partitions from $_lv" + done + + ok "Activated logical volumes" +} + +uuidfmt() +{ + echo $1 | \ + sed -E 's/^([0-9a-fA-F]{8})([0-9a-fA-F]{4})([0-9a-fA-F]{4})([0-9a-fA-F]{4})([0-9a-fA-F]{12})$/\1-\2-\3-\4-\5/' +} + +dm_verity_root_probe() +{ + local _root _hash + + [ -z "$roothash" ] && return 0 + + begin "Creating verity protected root" + + _root=$(getdisk PARTUUID=$(uuidfmt $(echo "$roothash" | cut -c 1-32))) + [ "$_root" ] || die "dm_verity_probe: Unknown verity root data device" + + _hash=$(getdisk PARTUUID=$(uuidfmt $(echo "$roothash" | cut -c 33-64))) + [ "$_hash" ] || die "dm_verity_probe: Unknown verity root hash device" + + veritysetup open "$_root" root "$_hash" "$roothash" \ + || die "dm_verity_probe: veritysetup failed" + + ok "Created verity protected root" +} + +mountcore + +debug_shell internal +internal_inflate + +debug_shell probe +initrd_probe +partuuids_probe +lvm_probe +dm_verity_root_probe + +debug_shell mount +mountroot + +debug_shell switchroot +switchroot diff --git a/board/common/initramfs/modules.d/10infix-init/module-setup.sh b/board/common/initramfs/modules.d/10infix-init/module-setup.sh new file mode 100755 index 000000000..011a90fb7 --- /dev/null +++ b/board/common/initramfs/modules.d/10infix-init/module-setup.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +progs="dmsetup kpartx jq lvm sgdisk sfdisk veritysetup" + +check() { + require_binaries $progs || return 1 + + return 0 +} + +depends() { + return 0 +} + +install() { + inst_multiple $progs + + cp "$moddir/init" "${initdir?}/init" + sed -e "s/@VERSION@/${INFIX_VERSION}/" -i "${initdir?}/init" +} diff --git a/board/x86_64/linux_defconfig b/board/x86_64/linux_defconfig index f0e062714..0d5a47032 100644 --- a/board/x86_64/linux_defconfig +++ b/board/x86_64/linux_defconfig @@ -29,10 +29,14 @@ CONFIG_CGROUP_BPF=y CONFIG_USER_NS=y CONFIG_SCHED_AUTOGROUP=y CONFIG_BLK_DEV_INITRD=y +CONFIG_INITRAMFS_SOURCE="infix-initramfs.cpio" +CONFIG_INITRAMFS_COMPRESSION_ZSTD=y CONFIG_KALLSYMS_ALL=y CONFIG_PROFILING=y CONFIG_SMP=y CONFIG_EFI=y +CONFIG_EFI_STUB=y +# CONFIG_EFI_HANDOVER_PROTOCOL is not set CONFIG_KPROBES=y # CONFIG_GCC_PLUGINS is not set CONFIG_MODULES=y @@ -309,7 +313,6 @@ CONFIG_VIRTIO_INPUT=y CONFIG_VIRTIO_MMIO=y CONFIG_VIRTIO_MMIO_CMDLINE_DEVICES=y CONFIG_EXT2_FS=y -CONFIG_EXT2_FS_POSIX_ACL=y CONFIG_EXT4_FS=y CONFIG_EXT4_FS_POSIX_ACL=y CONFIG_BTRFS_FS=y From 68bbb5864df97fa0d8999aa5b0af9dedcc1d3b8b Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Fri, 27 Feb 2026 23:18:09 +0000 Subject: [PATCH 16/19] qemu: Support running from DDI images run.sh has degenerated quite a bit over the years - in no small part due to the way the qemu-system argv is constructed. Cut our losses by taking a fresh look at this problem, now that lots of things need to change anyway. Keep the old script as-is for targets that still use ITB based images. --- board/common/qemu/ddi/Config.in.in | 339 +++++++++++++ board/common/qemu/ddi/run.sh | 582 +++++++++++++++++++++++ board/common/qemu/{ => itb}/Config.in.in | 0 board/common/qemu/{ => itb}/run.sh | 0 board/common/qemu/qemu.mk | 29 +- 5 files changed, 941 insertions(+), 9 deletions(-) create mode 100644 board/common/qemu/ddi/Config.in.in create mode 100755 board/common/qemu/ddi/run.sh rename board/common/qemu/{ => itb}/Config.in.in (100%) rename board/common/qemu/{ => itb}/run.sh (100%) diff --git a/board/common/qemu/ddi/Config.in.in b/board/common/qemu/ddi/Config.in.in new file mode 100644 index 000000000..79a35f28f --- /dev/null +++ b/board/common/qemu/ddi/Config.in.in @@ -0,0 +1,339 @@ +mainmenu "QEMU Virtualization" + +choice + prompt "Target Architecture" + default @ARCH@ + +config IX_QEMU_aarch64 + bool "AArch64 (little endian)" + +config IX_QEMU_arm + bool "AArch32 (little endian)" + +config IX_QEMU_riscv64 + bool "riscv64" + +config IX_QEMU_x86_64 + bool "x86_64" + +endchoice + +config IX_QEMU_ARCH + string + default "aarch64" if IX_QEMU_aarch64 + default "arm" if IX_QEMU_arm + default "riscv64" if IX_QEMU_riscv64 + default "x86_64" if IX_QEMU_x86_64 + +config IX_QEMU_VIRTIO_BUS + string + default "device" if IX_QEMU_arm + default "pci" + +config IX_QEMU_BIN_DDI + string + default "@IMAGE@" + +config IX_QEMU_BIN_DISK + string + default "@DISK@" + +config IX_QEMU_BIN_KERNEL + string + depends on IX_QEMU_LOADER_QEMU + default "../zImage" if IX_QEMU_arm + default "../Image" if IX_QEMU_aarch64 + default "../bzImage" if IX_QEMU_x86_64 + +config IX_QEMU_BIN_LVM_STUB + string + default "../lvm-stub.disk" + + +choice + prompt "Loader" + default IX_QEMU_LOADER_QEMU + +config IX_QEMU_LOADER_QEMU + bool "QEMU" + +config IX_QEMU_LOADER_OVMF + bool "OVMF (UEFI)" + +endchoice + +menu "OVMF" + depends on IX_QEMU_LOADER_OVMF + +config IX_QEMU_BIN_OVMF_CODE + string "CODE binary" + default "/usr/share/OVMF/OVMF_CODE_4M.secboot.fd" if IX_QEMU_OVMF_SB + default "/usr/share/OVMF/OVMF_CODE_4M.fd" + help + Path to the OVMF executable binary, passed as parallel FLASH + 0 (read-only). + +config IX_QEMU_BIN_OVMF_VARS + string "VARS binary" + default "/usr/share/OVMF/OVMF_VARS_4M.fd" + help + Path to the binary containing the OVMF non-volatile variable + store, passed as parallel FLASH 1. + +config IX_QEMU_OVMF_SB + bool "Secure boot" + default y + +config IX_QEMU_OVMF_SB_PK + string "Platform key (PK)" + depends on IX_QEMU_OVMF_SB + default "./infix-development.crt" + +config IX_QEMU_OVMF_SB_KEK + string "Key exchange key (KEK)" + depends on IX_QEMU_OVMF_SB + default "./infix-development.crt" + +config IX_QEMU_OVMF_SB_DB + string "Signature database keys (db)" + depends on IX_QEMU_OVMF_SB + default "./infix-development.crt" + +endmenu + +menu "Machine" + +config IX_QEMU_MACHINE + string "Machine" + default "q35,smm=on" if IX_QEMU_x86_64 + default "virt" + +config IX_QEMU_CPU + string "CPU" + default "host" if IX_QEMU_x86_64 + default "cortex-a53" if IX_QEMU_aarch64 + default "max" + +config IX_QEMU_RAM + string "RAM size (k/M/G)" + default "384M" + +choice + prompt "Console" + default IX_QEMU_CONSOLE_VIRTIO + +config IX_QEMU_CONSOLE_VIRTIO + bool "Virtio (hvc0)" + +config IX_QEMU_CONSOLE_SERIAL + bool "Serial (ttyS0/ttyAMA0)" + +endchoice + +config IX_QEMU_CONSOLE + string + default "hvc0" if IX_QEMU_CONSOLE_VIRTIO + default "ttyAMA0" if IX_QEMU_arm + default "ttyAMA0" if IX_QEMU_aarch64 + default "ttyS0" + +endmenu + +menu "Disks & Filesystems" + +config IX_QEMU_DISK_INITRD + bool "Use the DDI as initrd" + depends on IX_QEMU_LOADER_QEMU + help + Pass, and boot from, the DDI as the initial RAM disk. + + This is useful to test netbooting scenarios, e.g., in + combination with an empty system disk to simulate an initial + deployment. + +comment "System disk" + +choice + prompt "Interface" + default IX_QEMU_DISK_SYS_IF_VIRTIO + +config IX_QEMU_DISK_SYS_IF_VIRTIO + bool "virtio" + help + Attach system disks using virtio block devices. + +endchoice + +choice + prompt "Contents" + default IX_QEMU_DISK_SYS_SPLIT + +config IX_QEMU_DISK_SYS_EMPTY + bool "Empty disk" + help + Attach an empty disk. + + This is useful for testing things like installing Infix from a + USB mass storage device (see "USB Mass Storage" below). + +config IX_QEMU_DISK_SYS_FULL + bool "Full system disk" + help + Use the full system disk containing: + - EFI partition (and backup) with Barebox + - LVM PV with Infix installed + + This is useful when testing system upgrades, fallback + mechanisms related to corrupt disks etc. + + WARNING: Usage of this option is fraught with peril as + incremental builds of Infix are likely to cause corruption in + the QCoW2 layer. If you do not understand the meaning of this + you most likely to NOT want to make use of this feature. + +config IX_QEMU_DISK_SYS_NONE + bool "None" + help + Do not attach any system disk. + + This is useful for testing how Infix behaves when no persistent + storage is available - a common scenario during bringup of a + new board. + +config IX_QEMU_DISK_SYS_SPLIT + bool "Split DDI/LVM" + help + Attach one disk containing the EFI System partition and an + (initially empty) LVM stub for persistent storage; and the raw + DDI image in another disk. + + This is the simplest way of testing out incremental builds of + Infix. Since the DDI is attached as a raw read-only disk, there + is no risk of CoW corruption like with a full system disk. + + NOTE: In this setup, the size below refers to the size of the + LVM stub. + +endchoice + +config IX_QEMU_DISK_SYS_SIZE + string "Size (k/M/G)" + default "8G" + +comment "USB mass storage" + +choice + prompt "Contents" + default IX_QEMU_DISK_USB_NONE + +config IX_QEMU_DISK_USB_DISK + bool "Full system disk" + help + Attach the full system disk containing: + - EFI partition (and backup) with Barebox + - LVM PV with Infix installed + +config IX_QEMU_DISK_USB_NONE + bool "None" + help + Do not attach any disk over USB. + +endchoice + +config IX_QEMU_DISK_USB_SIZE + string "Size (k/M/G)" + depends on !IX_QEMU_DISK_USB_NONE + default "4G" + +comment "Host filesystem passthrough (9P)" + +config IX_QEMU_DISK_HOST + string "Path" + default "/tmp" + +endmenu + +menu "Networking" + +comment "Host attachment" + +choice + prompt "Mode" + default IX_QEMU_NET_USER + +config IX_QEMU_NET_BRIDGE + bool "Bridged" + +config IX_QEMU_NET_NONE + bool "None" + +config IX_QEMU_NET_ROCKER + bool "Rocker" + +config IX_QEMU_NET_TAP + bool "TAP" + +config IX_QEMU_NET_USER + bool "User" + +endchoice + +config IX_QEMU_NET_MODE + string + default "bridge" if IX_QEMU_NET_BRIDGE + default "none" if IX_QEMU_NET_NONE + default "rocker" if IX_QEMU_NET_ROCKER + default "tap" if IX_QEMU_NET_TAP + default "user" if IX_QEMU_NET_USER + + +config IX_QEMU_NET_BRIDGE_DEV + string "Bridge device" + depends on IX_QEMU_NET_BRIDGE + default "virbr0" + +config IX_QEMU_NET_USER_NETBOOT + bool "Enable netbooting" + depends on IX_QEMU_NET_USER + help + Supply the DDI as the DHCP bootfile and serve the images + directory over TFTP. + +config IX_QEMU_NET_USER_OPTS + string "User mode options" + depends on IX_QEMU_NET_USER + help + Extra -nic user, + +config IX_QEMU_NET_TAP_N + int "Number of TAPs" + depends on IX_QEMU_NET_TAP + default 1 + +config IX_QEMU_NET_PORTS + int "Number of Rocker switch ports" + depends on IX_QEMU_NET_ROCKER + default 10 + + +comment "VM attachment" + +config IX_QEMU_NET_MODEL + string "Interface model" + default "virtio-net-device" if IX_QEMU_arm + default "virtio-net-pci" + help + The default virtio NIC works for most use-cases, but if you + want to play with low-level stuff like ethtool, you might + want to test the Intel 82545EM driver, e1000. + + Note: ARM 32-bit uses virtio-net-device (MMIO) by default. + +endmenu + +config IX_QEMU_EXTRA + string "Extra QEMU options" + +config IX_QEMU_APPEND + string "Extra kernel options" + depends on IX_QEMU_LOADER_QEMU diff --git a/board/common/qemu/ddi/run.sh b/board/common/qemu/ddi/run.sh new file mode 100755 index 000000000..4f57ae7d8 --- /dev/null +++ b/board/common/qemu/ddi/run.sh @@ -0,0 +1,582 @@ +#!/bin/sh +# This script can be used to start an Infix OS image in Qemu. It reads +# either a .config, generated from Config.in, or qemu.cfg from a release +# tarball, for the required configuration data. +# +# Debian/Ubuntu users can change the configuration post-release, install +# the kconfig-frontends package: +# +# sudo apt install kconfig-frontends +# +# and then call this script with: +# +# ./run.sh -c +# +# To bring up a menuconfig dialog. Select `Exit` and save the changes. +# For more help, see:_ +# +# ./run.sh -h +# +# shellcheck disable=SC3037 + +# Add /sbin to PATH for mkfs.ext4 and such (not default in debian) +export PATH="/sbin:/usr/sbin:$PATH" + +qdir=$(dirname "$(readlink -f "$0")") +imgdir=$(readlink -f "${qdir}/..") +prognm=$(basename "$0") + +usage() +{ + cat <&2 + exit 1 +} + +binpath() +{ + case "$1" in + ./*|../*) + # Relative paths are relative to the location of .config + printf "$qdir/$1" + ;; + *) + printf "$1" + ;; + esac +} + +q() +{ + local _cmd="$1" + shift + + case "$_cmd" in + sect) + printf ' \\\n\t' >>"$qdir"/qemu.sh + + case $# in + 1) + printf -- "-$1" >>"$qdir"/qemu.sh + _delim=" " + ;; + 2) + printf -- "-$1 $2" >>"$qdir"/qemu.sh + _delim="," + ;; + *) + die "q sect: Invalid arguments" + ;; + esac + ;; + param) + [ $# -eq 2 ] || die "q param: Takes exactly two arguments" + printf -- "$_delim$1=$2" >>"$qdir"/qemu.sh + _delim="," + ;; + option) + [ $# -eq 1 ] || die "q option: Takes exactly one argument" + printf -- "$_delim$1" >>"$qdir"/qemu.sh + _delim="," + ;; + *) + die "q: Unknown command: $_cmd" + ;; + esac +} + +q_sect_device_virtio() +{ + q sect device virtio-$1-$IX_QEMU_VIRTIO_BUS +} + +q_add_disk() +{ + if [ "$IX_QEMU_DISK_SYS_IF_VIRTIO" ]; then + q_sect_device_virtio blk + q param drive "$1" + else + die "Unknown system disk interface" + fi +} + +q_sect_device_usb() +{ + if [ -z "$usb_bus_added" ]; then + q sect usb + q sect device usb-ehci + q param id ehci + usb_bus_added=YES + fi + + q sect device "$1" + q param bus ehci.0 +} + +qcowed() +{ + local _qcow="$qdir"/"$1".qcow2 + local _base _size + + if [ -f "$_qcow" ] && qemu-img check -q "$_qcow"; then + echo "$_qcow" + return + fi + + rm -f "$_qcow" + + case $# in + 2) + _size="$2" + + qemu-img create -q -f qcow2 "$_qcow" "$_size" \ + || die "Unable to create $1" + ;; + 3) + + _base="$2" + _size="$3" + + [ "$_size" = "auto" ] && _size= + + qemu-img create -q -f qcow2 -F raw \ + -o backing_file="$_base" "$_qcow" $_size \ + || die "Unable to create CoW layer for $_base" + ;; + *) + die "qcowed: usage: qcowed [] " + ;; + esac + + echo "$_qcow" +} + +append() +{ + echo -n " $*" >>"$qdir"/append +} + +appendroot() +{ + append root=/dev/mapper/root + append roothash=$(sfdisk -J $(binpath "$IX_QEMU_BIN_DDI") \ + | jq -r '.partitiontable.partitions | "\(.[0].uuid)-\(.[1].uuid)"' \ + | tr -d '-' | tr 'A-F' 'a-f') +} + +gen_machine() +{ + q sect machine + q param type "$IX_QEMU_MACHINE" + q param accel kvm:tcg + + q sect cpu "$IX_QEMU_CPU" + + q sect m + q param size "$IX_QEMU_RAM" +} + +gen_pflash_ovmf_code() +{ + q sect drive + q param id ovmf-code + q param format raw + q param if pflash + q param unit 0 + q param file $(binpath "$IX_QEMU_BIN_OVMF_CODE") + q param readonly on +} + +gen_pflash_ovmf_vars() +{ + local _src=$(binpath "$IX_QEMU_BIN_OVMF_VARS") + local _dst="$qdir"/ovmf-vars-$(grep _OVMF_ "$qdir"/.config | sha256sum | head -c 8).fd + local _guid=$(uuidgen) + + if ! [ -f "$_dst" ]; then + [ -f "$_src" ] || die "OVMF Variable template ($_src) does not exist" + cp "$_src" "$_dst" + + if [ "$IX_QEMU_OVMF_SB" ]; then + command virt-fw-vars \ + || die "Please install virt-fw-vars to support injection of OVMF secure boot variables" + + virt-fw-vars \ + --inplace "$_dst" \ + --set-pk $_guid $(binpath "$IX_QEMU_OVMF_SB_PK") \ + --add-kek $_guid $(binpath "$IX_QEMU_OVMF_SB_KEK") \ + --add-db $_guid $(binpath "$IX_QEMU_OVMF_SB_DB") \ + --secure-boot \ + || die "Failed to inject secure boot keys to OVMF variable image" + fi + fi + + q sect drive + q param id ovmf-vars + q param format raw + q param if pflash + q param unit 1 + q param file "$_dst" +} + +gen_loader() +{ + if [ "$IX_QEMU_LOADER_QEMU" ]; then + q sect kernel $(binpath "$IX_QEMU_BIN_KERNEL") + appendroot + elif [ "$IX_QEMU_LOADER_OVMF" ]; then + gen_pflash_ovmf_code + gen_pflash_ovmf_vars + + if [ "$IX_QEMU_OVMF_SB" ]; then + q sect global + q param driver cfi.pflash01 + q param property secure + q param value on + fi + fi +} + +gen_serial() +{ + q sect display none + + q_sect_device_virtio serial + + q sect chardev stdio + q param id console0 + q param mux on + + q sect mon + q param chardev console0 + + case "$IX_QEMU_CONSOLE" in + hvc0) + q sect device virtconsole + q param nr 0 + q param name console + q param chardev console0 + ;; + ttyS0|ttyAMA0) + q sect serial chardev:console0 + ;; + *) + die "Unsupported console: $IX_QEMU_CONSOLE" + ;; + esac + + append console="$IX_QEMU_CONSOLE" + + [ "$V" ] && append debug || append loglevel=4 +} + +gpt_uuid() +{ + sgdisk "$1" -p | awk ' + BEGIN { err = 1; } END { exit(err); } + /^Creating new GPT/ { exit; } + + /^Disk identifier \(GUID\): / { + print($4); err = 0; exit; + }' || die "Unable to determine GPT UUID of $1" +} + +gen_disk_sys_empty() +{ + q sect drive + q param id disk-sys + q param format qcow2 + q param if none + q param file $(qcowed disk-sys "$IX_QEMU_DISK_SYS_SIZE") + + q_add_disk disk-sys +} + +gen_disk_sys_full() +{ + q sect drive + q param id disk-sys + q param format qcow2 + q param if none + q param file $(qcowed disk-sys $(binpath "$IX_QEMU_BIN_DISK") \ + "$IX_QEMU_DISK_SYS_SIZE") + + q_add_disk disk-sys +} + +gen_disk_sys_split() +{ + # LVM stub + q sect drive + q param id disk-stub + q param format qcow2 + q param if none + q param file $(qcowed disk-stub $(binpath "$IX_QEMU_BIN_LVM_STUB") \ + "$IX_QEMU_DISK_SYS_SIZE") + + q_add_disk disk-stub + + # DDI (ro) + q sect drive + q param id disk-ddi + q param format raw + q param file $(binpath "$IX_QEMU_BIN_DDI") + q param if none + q param read-only on + + q_add_disk disk-ddi +} + +gen_disk_sys() +{ + [ "$IX_QEMU_DISK_SYS_NONE" ] && return + + if [ "$IX_QEMU_DISK_SYS_EMPTY" ]; then + gen_disk_sys_empty + elif [ "$IX_QEMU_DISK_SYS_FULL" ]; then + gen_disk_sys_full + elif [ "$IX_QEMU_DISK_SYS_SPLIT" ]; then + gen_disk_sys_split + else + die "Unknown system disk mode" + fi +} + +gen_disk_usb() +{ + [ "$IX_QEMU_DISK_USB_NONE" ] && return + + if [ "$IX_QEMU_DISK_USB_DISK" ]; then + q sect drive + q param id disk-usb + q param format qcow2 + q param file $(qcowed disk-usb \ + $(binpath "$IX_QEMU_BIN_DISK") \ + "$IX_QEMU_DISK_USB_SIZE") + q param if none + + q_sect_device_usb usb-storage + q param drive disk-usb + else + die "Unknown USB attachment" + fi +} + +gen_disk_host() +{ + [ -d "$IX_QEMU_DISK_HOST" ] || return + + q sect virtfs local + q param path "$IX_QEMU_DISK_HOST" + q param security_model none + q param writeout immediate + q param mount_tag host +} + +gen_disk_initrd() +{ + [ "$IX_QEMU_DISK_INITRD" ] || return + + q sect initrd $(binpath "$IX_QEMU_BIN_DDI") +} + +internal_is_available() +{ + [ "$IX_QEMU_DISK_SYS_FULL" ] || [ "$IX_QEMU_DISK_SYS_SPLIT" ] \ + || [ "$IX_QEMU_DISK_USB_DISK" ] +} + +gen_disks() +{ + gen_disk_sys + gen_disk_usb + gen_disk_host + + gen_disk_initrd + + internal_is_available || append rd.nointernal +} + +gen_net_dev() +{ + local _name="$1" + local _idx="$2" + local _mac=$(printf "02:00:00:de:ad:%02x" "$_idx") + + echo "$_name $_mac" >>"$qdir"/mactab + + q sect device "$IX_QEMU_NET_MODEL" + q param netdev "$_name" + q param mac "$_mac" +} + +gen_net_user() +{ + [ "$IX_QEMU_NET_USER" ] || return + + q sect netdev user + q param id e1 + + if [ "$IX_QEMU_NET_USER_NETBOOT" ]; then + q param tftp $(dirname $(readlink -f $(binpath "$IX_QEMU_BIN_DDI"))) + q param bootfile $(basename "$IX_QEMU_BIN_DDI") + fi + + if [ "$IX_QEMU_NET_USER_OPTS" ]; then + q option "$IX_QEMU_NET_USER_OPTS" + fi + + gen_net_dev e1 1 +} + +gen_net() +{ + :> "$qdir"/mactab + q sect fw_cfg + q param name opt/mactab + q param file "$qdir"/mactab + + gen_net_user +} + +gen_gdb() +{ + # Create a UNIX socket on the host that is connected to a virtio + # console in the guest, which gdbserver can attach to for + # userspace debugging. + q sect chardev socket + q param id gdbserver + q param path "$qdir"/gdbserver.sock + q param server on + q param wait off + + q sect device virtserialport + q param nr 1 + q param name gdbserver + q param chardev gdbserver + + # Create a UNIX socket on the host that is connected to QEMU's GDB + # stub, for bootloader/kernel debugging. + q sect chardev socket + q param id gdbqemu + q param path "$qdir"/gdbqemu.sock + q param server on + q param wait off + + q sect gdb chardev:gdbqemu +} + +gen_all() +{ + local _append + + : >"$qdir"/append + cat <"$qdir"/qemu.sh +#!/bin/sh + +echo "Starting Qemu :: Ctrl-a x -- exit | Ctrl-a c -- toggle console/monitor" + +line=\$(stty -g) +stty raw +trap 'stty "\$line"' EXIT INT TERM + +qemu-system-$IX_QEMU_ARCH -nodefaults \\ +EOF + chmod +x "$qdir"/qemu.sh + + gen_machine + gen_loader + gen_serial + gen_disks + gen_net + gen_gdb + + if [ "$IX_QEMU_LOADER_QEMU" ]; then + _append=$(cat "$qdir"/append) + q sect append "\"$_append $IX_QEMU_APPEND $*\"" + fi + + echo >>"$qdir"/qemu.sh +} + +menuconfig() +{ + command -v kconfig-mconf >/dev/null \ + || die "cannot find kconfig-mconf for menuconfig" + + CONFIG_= KCONFIG_CONFIG="$qdir"/.config \ + kconfig-mconf "$qdir"/Config.in +} + +_generate=YES + +while getopts "0cGh-" opt; do + case ${opt} in + 0) + echo "Clearing all copy-on-write layers" >&2 + rm -f "$qdir"/*.qcow2 + rm -f "$qdir"/ovmf-vars-*.fd + exit 0 + ;; + c) + menuconfig + ;; + G) + _generate= + ;; + h) + usage && exit 0 + ;; + -) + break + ;; + *) + usage && exit 1 + ;; + esac +done +shift $((OPTIND - 1)) + +# shellcheck disable=SC1090 +. "$qdir"/.config + +[ "$_generate" ] && gen_all "$*" + +exec "$qdir"/qemu.sh diff --git a/board/common/qemu/Config.in.in b/board/common/qemu/itb/Config.in.in similarity index 100% rename from board/common/qemu/Config.in.in rename to board/common/qemu/itb/Config.in.in diff --git a/board/common/qemu/run.sh b/board/common/qemu/itb/run.sh similarity index 100% rename from board/common/qemu/run.sh rename to board/common/qemu/itb/run.sh diff --git a/board/common/qemu/qemu.mk b/board/common/qemu/qemu.mk index bdee41a05..694c38d6b 100644 --- a/board/common/qemu/qemu.mk +++ b/board/common/qemu/qemu.mk @@ -4,12 +4,16 @@ # ################################################################################ -QEMU_SCRIPTS_DIR := $(pkgdir) -QEMU_SCRIPTS_NAME := qemu-scripts -QEMU_SCRIPTS_TYPE := rootfs +qemu-scripts-dir := $(pkgdir)/$(if $(IX_IMAGE_DDI),ddi,itb) +qemu-image := ../$(INFIX_ARTIFACT).$(if $(IX_IMAGE_DDI),raw,qcow2) +qemu-disk := $(if $(IX_IMAGE_DDI_DISK),../$(INFIX_ARTIFACT).disk) +qemu-esp := $(if $(IX_IMAGE_BAREBOX_ESP),../barebox-esp.vfat) +qemu-extra := $(if $(IX_IMAGE_DDI),$(BINARIES_DIR)/qemu/infix-development.crt) + +qemu-kconfig-prefix := $(if $(IX_IMAGE_DDI),,CONFIG_) qemu-kconfig = \ - CONFIG_="CONFIG_" \ + CONFIG_="$(qemu-kconfig-prefix)" \ BR2_CONFIG="$(BINARIES_DIR)/qemu/.config" \ $(BUILD_DIR)/buildroot-config/$(1) $(2) "$(BINARIES_DIR)/qemu/Config.in" @@ -26,23 +30,30 @@ run-menuconfig: $(BUILD_DIR)/buildroot-config/mconf qemu-scripts: \ $(BINARIES_DIR)/qemu/run.sh \ $(BINARIES_DIR)/qemu/Config.in \ - $(BINARIES_DIR)/qemu/.config + $(BINARIES_DIR)/qemu/.config \ + $(qemu-extra) -$(BINARIES_DIR)/qemu/run.sh: $(QEMU_SCRIPTS_DIR)/run.sh +$(BINARIES_DIR)/qemu/run.sh: $(qemu-scripts-dir)/run.sh @$(call IXMSG,"Installing QEMU scripts") @mkdir -p $(dir $@) @cp $< $@ -$(BINARIES_DIR)/qemu/Config.in: $(QEMU_SCRIPTS_DIR)/Config.in.in +$(BINARIES_DIR)/qemu/Config.in: $(qemu-scripts-dir)/Config.in.in @mkdir -p $(dir $@) @sed \ - -e "s:@ARCH@:IX_QEMU_$(BR2_ARCH):" \ - -e "s:@DISK_IMG@:../$(INFIX_ARTIFACT).qcow2:" \ + -e "s:@ARCH@:IX_QEMU_$(BR2_ARCH):g" \ + -e "s:@IMAGE@:$(qemu-image):g" \ + -e "s:@DISK@:$(qemu-disk):g" \ < $< >$@ $(BINARIES_DIR)/qemu/.config: $(BINARIES_DIR)/qemu/Config.in @$(call qemu-kconfig,conf,--olddefconfig) @rm -f $@.old +$(BINARIES_DIR)/qemu/infix-development.crt: \ +$(BR2_EXTERNAL_INFIX_PATH)/board/common/signing-keys/development/infix.crt + @mkdir -p $(dir $@) + @cp $< $@ + TARGETS_ROOTFS += qemu-scripts endif From 61f68166bab3e8a9d218a004e5e1d7ef73fa5b95 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Thu, 8 Oct 2026 09:29:46 +0000 Subject: [PATCH 17/19] {conf,stat}d: Add Barebox support With DDI images, test-mode and other marker files are stored on /boot/efi instead of /mnt/aux. Add a --with-marker-dir configure option, and select the right directory based on IX_IMAGE_DDI. Add a glue script to manage the priority-based approach used by the Barebox bootchooser to determine the boot order. --- package/confd-test-mode/confd-test-mode.mk | 1 + package/confd/confd.mk | 1 + src/confd/bin/Makefile.am | 4 +- src/confd/bin/bareboxcfg | 68 +++++++++++++++++++ src/confd/bin/{gen-config => gen-config.in} | 2 +- src/confd/configure.ac | 8 +++ src/confd/src/main.c | 10 +-- src/confd/src/system-software.c | 6 +- src/statd/python/yanger/ietf_system.py | 15 ++-- src/test-mode/configure.ac | 6 ++ src/test-mode/src/test-mode.c | 4 +- .../+usr+libexec+confd+bareboxcfg_--get-order | 1 + .../grub-editenv_+mnt+aux+grub+grubenv_list | 7 -- 13 files changed, 103 insertions(+), 30 deletions(-) create mode 100755 src/confd/bin/bareboxcfg rename src/confd/bin/{gen-config => gen-config.in} (99%) create mode 100644 test/case/statd/system/system/run/+usr+libexec+confd+bareboxcfg_--get-order delete mode 100644 test/case/statd/system/system/run/grub-editenv_+mnt+aux+grub+grubenv_list diff --git a/package/confd-test-mode/confd-test-mode.mk b/package/confd-test-mode/confd-test-mode.mk index 1a9f040c9..3c991a13c 100644 --- a/package/confd-test-mode/confd-test-mode.mk +++ b/package/confd-test-mode/confd-test-mode.mk @@ -12,6 +12,7 @@ CONFD_TEST_MODE_LICENSE_FILES = LICENSE CONFD_TEST_MODE_REDISTRIBUTE = NO CONFD_TEST_MODE_DEPENDENCIES = sysrepo libite libyang confd CONFD_TEST_MODE_AUTORECONF = YES +CONFD_TEST_MODE_CONF_OPTS = --with-marker-dir=$(if $(IX_IMAGE_DDI),/boot/efi,/mnt/aux) CONFD_TEST_MODE_SYSREPO_SHM_PREFIX = sr_buildroot$(subst /,_,$(CONFIG_DIR))_test_mode COMMON_SYSREPO_ENV = \ diff --git a/package/confd/confd.mk b/package/confd/confd.mk index 05b545aa0..a94675784 100644 --- a/package/confd/confd.mk +++ b/package/confd/confd.mk @@ -13,6 +13,7 @@ CONFD_REDISTRIBUTE = NO CONFD_DEPENDENCIES = host-sysrepo sysrepo rousette netopeer2 jansson libite sysrepo libsrx libglib2 libev sysklogd watchdogd CONFD_AUTORECONF = YES CONFD_CONF_OPTS += --disable-silent-rules --with-crypt=$(BR2_PACKAGE_CONFD_DEFAULT_CRYPT) +CONFD_CONF_OPTS += --with-marker-dir=$(if $(IX_IMAGE_DDI),/boot/efi,/mnt/aux) CONFD_SYSREPO_SHM_PREFIX = sr_buildroot$(subst /,_,$(CONFIG_DIR))_confd define CONFD_CONF_ENV diff --git a/src/confd/bin/Makefile.am b/src/confd/bin/Makefile.am index 4b2ad2767..51975838f 100644 --- a/src/confd/bin/Makefile.am +++ b/src/confd/bin/Makefile.am @@ -1,5 +1,5 @@ -pkglibexec_SCRIPTS = gen-config gen-hostname gen-interfaces gen-motd gen-hardware \ - gen-version mstpd-wait-online wait-interface +pkglibexec_SCRIPTS = bareboxcfg gen-config gen-hostname gen-interfaces gen-motd \ + gen-hardware gen-version mstpd-wait-online wait-interface pkglibexec_PROGRAMS = pppmon pppmon_SOURCES = pppmon.c pppmon_CFLAGS = $(libite_CFLAGS) diff --git a/src/confd/bin/bareboxcfg b/src/confd/bin/bareboxcfg new file mode 100755 index 000000000..20a111245 --- /dev/null +++ b/src/confd/bin/bareboxcfg @@ -0,0 +1,68 @@ +#!/bin/sh + +set -e + +alias bbs="barebox-state -i /etc/barebox/state.dtb" + +LOADERINFO=/sys/firmware/efi/efivars/LoaderInfo-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f + +available() +{ + [ -f $LOADERINFO ] || exit 1 + tr -c -d '[a-z]' <$LOADERINFO | grep -q '^barebox' +} + +slotprios() +{ + bbs -d | awk -F. ' + /^bootstate\.[a-z0-9-]+\.priority=/ { + sub("priority=", "", $3); + sub(/^internal-/, "", $2); + print($3, $2); + }' +} + +order_get() +{ + local _enabled_slots=$(slotprios | grep -v '^0 ' | sort -nr | cut -d" " -f2) + echo $_enabled_slots +} + +order_set_args() +{ + local _attempts + local _base + + slotprios | while read prio slot; do + case "$slot" in + "primary"|"secondary") _base="bootstate.internal-${slot}" ;; + *) _base="bootstate.${slot}" ;; + esac + + _attempts=3 + + echo -n " -s ${_base}.priority=" + case "$slot" in + "$1") echo -n $(($# * 10 - 0));; + "$2") echo -n $(($# * 10 - 10));; + "$3") echo -n $(($# * 10 - 20));; + *) echo -n 0; _attempts=0;; + esac + + echo -n " -s ${_base}.remaining_attempts=${_attempts}" + done +} + +order_set() +{ + eval "bbs $(order_set_args $@)" +} + +cmd=$1 +shift + +case $cmd in + --available) available ;; + --get-order) order_get ;; + --set-order) order_set "$@" ;; +esac diff --git a/src/confd/bin/gen-config b/src/confd/bin/gen-config.in similarity index 99% rename from src/confd/bin/gen-config rename to src/confd/bin/gen-config.in index d58e8b7b0..b2e5b1b55 100755 --- a/src/confd/bin/gen-config +++ b/src/confd/bin/gen-config.in @@ -121,7 +121,7 @@ gen_factory_cfg log "Starting up, calling gen_failure_cfg()" gen_failure_cfg -if [ -f "/mnt/aux/test-mode" ]; then +if [ -f "@MARKER_DIR@/test-mode" ]; then gen_test_cfg fi diff --git a/src/confd/configure.ac b/src/confd/configure.ac index 28f0c6e2f..3294a3209 100644 --- a/src/confd/configure.ac +++ b/src/confd/configure.ac @@ -7,6 +7,7 @@ AM_SILENT_RULES(yes) AC_CONFIG_FILES([ Makefile bin/Makefile + bin/gen-config bin/gen-version share/Makefile share/factory.d/Makefile @@ -85,6 +86,12 @@ AC_ARG_WITH(crypt, AS_HELP_STRING([--with-crypt=crypt], [Default crypt for $0$cleartext, default: yescrypt]), [crypt=$withval], [crypt="yescrypt"]) +AC_ARG_WITH([marker-dir], + [AS_HELP_STRING([--with-marker-dir=DIR], [Directory holding test-mode marker files @<:@/mnt/aux@:>@])], + [markerdir="$withval"], [markerdir=/mnt/aux]) +AC_DEFINE_UNQUOTED([MARKER_DIR], ["$markerdir"], [Directory holding test-mode marker files]) +AC_SUBST([MARKER_DIR], [$markerdir]) + AS_IF([test "x$enable_containers" = "xyes"], [ AC_DEFINE(CONTAINERS, 1, [Built with container support])]) @@ -203,6 +210,7 @@ Optional features: SNMP support .........: $enable_snmp Login shell ..........: $login_shell Default crypt algo ...: $crypt + Marker directory .....: $markerdir ------------- Compiler version -------------- $($CC --version || true) diff --git a/src/confd/src/main.c b/src/confd/src/main.c index 6e24690c4..1278bd977 100644 --- a/src/confd/src/main.c +++ b/src/confd/src/main.c @@ -54,7 +54,7 @@ #define MIGRATED_PATH "/run/confd-migrated.cfg" /* Set when startup-config fails, next boot goes to fail-secure, issue #1637 */ -#define FAILED_PATH "/mnt/aux/startup-config.failed" +#define FAILED_PATH MARKER_DIR"/startup-config.failed" /* * Set a finit condition in the usr/ namespace, e.g. @@ -603,7 +603,7 @@ static void handle_startup_failure(sr_session_ctx_t *sess, const char *failure_p */ static void maybe_enable_test_mode(void) { - if (fexist("/mnt/aux/test-mode")) { + if (fexist(MARKER_DIR "/test-mode")) { int rc; conout(3, "Enabling test mode"); @@ -635,9 +635,9 @@ static int bootstrap_config(sr_conn_ctx_t *conn, sr_session_ctx_t *sess, } /* Test mode support */ - if (fexist("/mnt/aux/test-mode")) { - if (fexist("/mnt/aux/test-override-startup")) { - unlink("/mnt/aux/test-override-startup"); + if (fexist(MARKER_DIR "/test-mode")) { + if (fexist(MARKER_DIR "/test-override-startup")) { + unlink(MARKER_DIR "/test-override-startup"); config_path = startup_path; } else { NOTE("Test mode detected, switching to test-config"); diff --git a/src/confd/src/system-software.c b/src/confd/src/system-software.c index a714944c5..fdbc24adc 100644 --- a/src/confd/src/system-software.c +++ b/src/confd/src/system-software.c @@ -80,9 +80,9 @@ static int infix_system_sw_set_boot_order(sr_session_ctx_t *session, uint32_t su ERROR("Set-boot-order: Failed to set boot order in U-Boot"); return SR_ERR_INTERNAL; } - } else if (fexist("/mnt/aux/grub/grubenv")) { - if (systemf("grub-editenv /mnt/aux/grub/grubenv set ORDER=\"%s\"", boot_order)) { - ERROR("Set-boot-order: Failed to set boot order in Grub"); + } else if (!systemf("/usr/libexec/confd/bareboxcfg --available")) { + if (systemf("/usr/libexec/confd/bareboxcfg --set-order %s", boot_order)) { + ERROR("Set-boot-order: Failed to set boot order in Barebox"); return SR_ERR_INTERNAL; } } else { diff --git a/src/statd/python/yanger/ietf_system.py b/src/statd/python/yanger/ietf_system.py index 671c4beca..89c100950 100644 --- a/src/statd/python/yanger/ietf_system.py +++ b/src/statd/python/yanger/ietf_system.py @@ -13,13 +13,8 @@ def uboot_get_boot_order(): raise Exception -def grub_get_boot_order(): - data = HOST.run_multiline("grub-editenv /mnt/aux/grub/grubenv list".split(), []) - for line in data: - if "ORDER" in line: - return line.split("=")[1].strip().split() - - raise Exception +def barebox_get_boot_order(): + return HOST.run_multiline("/usr/libexec/confd/bareboxcfg --get-order".split())[0].split() def get_boot_order(): order = None @@ -29,7 +24,7 @@ def get_boot_order(): pass try: if order is None: - order = grub_get_boot_order() + order = barebox_get_boot_order() except: pass @@ -156,7 +151,7 @@ def add_software_slots(out, data): for key, value in slot.items(): new = {} new["name"] = key - new["bootname"] = slot[key].get("bootname") + new["bootname"] = slot[key].get("bootname").replace("internal-", "") new["class"] = slot[key].get("class") new["state"] = slot[key].get("state") new["bundle"] = {} @@ -254,7 +249,7 @@ def add_software(out): data = HOST.run_json(["rauc", "status", "--detailed", "--output-format=json"], {}) software["compatible"] = data.get("compatible", "") software["variant"] = data.get("variant", "") - software["booted"] = data.get("booted", "") + software["booted"] = data.get("booted", "").replace("internal-", "") boot_order = get_boot_order() if not boot_order is None: software["boot-order"] = boot_order diff --git a/src/test-mode/configure.ac b/src/test-mode/configure.ac index 71280daa0..769658c01 100644 --- a/src/test-mode/configure.ac +++ b/src/test-mode/configure.ac @@ -14,6 +14,11 @@ LT_INIT PKG_PROG_PKG_CONFIG +AC_ARG_WITH([marker-dir], + [AS_HELP_STRING([--with-marker-dir=DIR], [Directory holding test-mode marker files @<:@/mnt/aux@:>@])], + [markerdir="$withval"], [markerdir=/mnt/aux]) +AC_DEFINE_UNQUOTED([MARKER_DIR], ["$markerdir"], [Directory holding test-mode marker files]) + PKG_CHECK_MODULES([sysrepo], [sysrepo >= 2.2.36]) PKG_CHECK_MODULES([libite], [libite >= 2.5.0]) @@ -53,6 +58,7 @@ cat < Date: Thu, 8 Oct 2026 11:56:03 +0000 Subject: [PATCH 18/19] test: Use DDI images for qeneth testing --- test/.env | 2 +- test/docker/Dockerfile | 3 + test/env | 16 +--- test/inject-test-mode | 108 ++++++++++++---------- test/templates/infix-bios-x86_64.mustache | 6 +- test/test.mk | 3 +- 6 files changed, 72 insertions(+), 66 deletions(-) diff --git a/test/.env b/test/.env index d92081768..e18f2d57a 100644 --- a/test/.env +++ b/test/.env @@ -2,7 +2,7 @@ # shellcheck disable=SC2034,SC2154 # Current container image -INFIX_TEST=ghcr.io/kernelkit/infix-test:2.14 +INFIX_TEST=ghcr.io/kernelkit/infix-test:2.15 ixdir=$(readlink -f "$testdir/..") logdir=$(readlink -f "$testdir/.log") diff --git a/test/docker/Dockerfile b/test/docker/Dockerfile index 56e2dc193..0b2d49381 100644 --- a/test/docker/Dockerfile +++ b/test/docker/Dockerfile @@ -32,17 +32,20 @@ RUN apk add --no-cache \ libyang-dev \ linux-headers \ make \ + mtools \ ncurses-dev \ net-snmp-tools \ nmap \ openssh-client \ openssl \ + ovmf \ pandoc-cli \ pkgconf \ python3-dev \ qemu-img \ qemu-system-x86_64 \ ruby-mustache \ + sfdisk \ skopeo \ socat \ squashfs-tools \ diff --git a/test/env b/test/env index 2bf3fe6c0..f57da07b1 100755 --- a/test/env +++ b/test/env @@ -81,7 +81,7 @@ get_base_img() { local files="$1" local base_img_file - base_img_file=$(echo "$files" | tr ' ' '\n' | grep -- '.qcow2$') + base_img_file=$(echo "$files" | tr ' ' '\n' | grep -- '.disk$') echo "$envdir/qeneth/$(basename "$base_img_file")" } @@ -102,17 +102,11 @@ start_topology() ln -sf "$file" "$envdir/qeneth/$filename" done - base_img_qcow2=$(get_base_img "$files") - base_img_disk="${base_img_qcow2%.qcow2}.disk" + base_img_disk=$(get_base_img "$files") + test_img_disk="${base_img_disk%.disk}-test.disk" + $testdir/inject-test-mode "$base_img_disk" "$test_img_disk" - test_img_disk="${base_img_qcow2%.qcow2}-test.disk" - test_img_qcow2="${test_img_disk%.disk}.qcow2" - - qemu-img convert -f qcow2 -O raw "$base_img_qcow2" "$base_img_disk" - $testdir/inject-test-mode -b "$base_img_disk" -o "$test_img_disk" - qemu-img convert -f raw -O qcow2 "$test_img_disk" "$test_img_qcow2" - - img_name=$(basename $test_img_qcow2) + img_name=$(basename $test_img_disk) sed -i "s/qn_image=\".*\"/qn_image=\"$img_name\"/" "$envdir/qeneth/topology.dot.in" (cd "$envdir/qeneth/" && $qeneth generate && $qeneth start) diff --git a/test/inject-test-mode b/test/inject-test-mode index 2f499b5b5..138eea582 100755 --- a/test/inject-test-mode +++ b/test/inject-test-mode @@ -1,54 +1,64 @@ #!/bin/sh -# This script injects a "test-mode" file into a copy of the main disk image. -# It begins by parsing the partition table of the disk image to identify the -# 'aux' partition, which is then extracted. An empty 'test-mode' file is -# injected into the extracted partition, and the modified partition is written -# back to the output image. The output image can subsequently be used as a -# backing image for Copy-on-Write (QCOW) images utilized by Qeneth. +# This script injects a "test-mode" file into a copy of the main disk +# image. It begins by parsing the partition table of the disk image +# to identify either the 'aux' or 'esp' partition, which is then +# extracted. An empty 'test-mode' file is injected into the extracted +# partition, and the modified partition is written back to the output +# image. The output image can subsequently be used as a backing image +# for Copy-on-Write (QCOW) images utilized by Qeneth. set -e -while getopts "b:o:" opt; do - case $opt in - b) base_img="$OPTARG" ;; # Base image (Original image) - o) output_img="$OPTARG" ;; # Output image (Backing image for QCoW images) - *) echo "Usage: $0 -b -o " ; exit 1 ;; - esac -done - -if [ -z "$base_img" ] || [ -z "$output_img" ]; then - echo "Both -b (base image) and -o (output image) parameters are required." - exit 1 -fi - -rm -f "$output_img" -if ! cp "$base_img" "$output_img"; then - echo "Error: Failed to copy $base_img to $output_img" - exit 1 -fi - -if ! part_table=$(fdisk -l "$output_img" 2>/dev/null); then - echo "Error: Failed to read partition table from $output_img" - exit 1 -fi - -aux_line=$(echo "$part_table" | grep 'aux') -if [ -z "$aux_line" ]; then - echo "Error: 'aux' partition not found in $output_img" - exit 1 -fi - -start=$(echo "$aux_line" | awk '{print $2}') -end=$(echo "$aux_line" | awk '{print $3}') -count=$(($end - $start + 1)) -block_size=$(echo "$part_table" | grep "Logical sector size" | awk '{print $4}') - -dd if="$output_img" of="tmpaux" skip="$start" count="$count" bs="$block_size" status=none - -touch tmp-empty-file -e2cp tmp-empty-file tmpaux:/test-mode -rm tmp-empty-file - -dd of="$output_img" if="tmpaux" seek="$start" count="$count" bs="$block_size" status=none conv=notrunc -rm tmpaux +die() +{ + echo "ERROR: $*" >&2 + exit 1 +} + +workdir=$(mktemp -d) +trap 'rm -rf "$workdir"' EXIT INT TERM + + +baseimg="$1" +testimg="$2" + +basegpt="$workdir"/basegpt.json +sfdisk -J "$baseimg" >"$basegpt" + +lbsize=$(jq -r .partitiontable.sectorsize $basegpt) + +jq -r '.partitiontable.partitions[] + | select(.name == "aux" or .name == "esp") + | "\(.name) \(.start) \(.size)"' $basegpt | { + read part start size + + [ "$part" ] || die "Disk must contain either aux or esp partition" + + + dd status=none \ + if="$baseimg" \ + bs=$lbsize skip=$start count=$size \ + of="$workdir"/markerpart \ + || die "Unable to extract marker partition" + + touch "$workdir"/empty + + case "$part" in + aux) + e2cp "$workdir"/empty "$workdir"/markerpart:/test-mode \ + || die "Unable to inject test-mode in aux" + ;; + esp) + mcopy -i "$workdir"/markerpart "$workdir"/empty ::/test-mode \ + || die "Unable to inject test-mode in esp" + ;; + esac + + cp -f "$baseimg" "$testimg" || die "Unable to create $testimg" + dd status=none conv=notrunc \ + if="$workdir"/markerpart \ + bs=$lbsize seek=$start count=$size \ + of="$testimg" \ + || die "Unable to inject markerpartition in $testimg" +} diff --git a/test/templates/infix-bios-x86_64.mustache b/test/templates/infix-bios-x86_64.mustache index eddecec07..2666380cc 100644 --- a/test/templates/infix-bios-x86_64.mustache +++ b/test/templates/infix-bios-x86_64.mustache @@ -4,12 +4,12 @@ con=hvc0 tty -s && con=hvc2 img={{#qn_image}}{{qn_image}}{{/qn_image}}{{^qn_image}}infix-x86_64-disk.img{{/qn_image}} -bios={{#qn_bios}}{{qn_bios}}{{/qn_bios}}{{^qn_bios}}OVMF.fd{{/qn_bios}} +bios={{#qn_bios}}{{qn_bios}}{{/qn_bios}}{{^qn_bios}}/usr/share/OVMF/OVMF.fd{{/qn_bios}} imgdir=. {{> inc/infix-usb}} origimg=$(realpath $img) -qemu-img create -f qcow2 -o backing_file=$origimg -F qcow2 {{name}}.qcow2 +qemu-img create -b $origimg -F raw -f qcow2 {{name}}.qcow2 4G {{> inc/infix-mactab}} @@ -20,7 +20,7 @@ mkfifo /tmp/{{name}}-gps.in /tmp/{{name}}-gps.out 2>/dev/null mkfifo /tmp/{{name}}-gps1.in /tmp/{{name}}-gps1.out 2>/dev/null {{/qn_gps}} -exec qemu-system-x86_64 -M pc,accel=kvm:tcg -cpu max \ +exec qemu-system-x86_64 -M q35,smm=on,accel=kvm:tcg -cpu max \ -m {{#qn_mem}}{{qn_mem}}{{/qn_mem}}{{^qn_mem}}256M{{/qn_mem}} \ {{> ../qeneth/templates/inc/qemu-links}} {{> inc/infix-fwcfg}} diff --git a/test/test.mk b/test/test.mk index fdc10ff27..8973f963e 100644 --- a/test/test.mk +++ b/test/test.mk @@ -24,8 +24,7 @@ mode-run := -t $(BINARIES_DIR)/qemu.dot mode := $(mode-$(TEST_MODE)) pkg-$(ARCH) := -p $(O)/images/$(INFIX_ARTIFACT).pkg -binaries-$(ARCH) := $(INFIX_ARTIFACT).qcow2 -binaries-x86_64 += OVMF.fd +binaries-$(ARCH) := $(INFIX_ARTIFACT).disk binaries := $(foreach bin,$(binaries-$(ARCH)),-f $(BINARIES_DIR)/$(bin)) # Common transport override for minimal defconfigs From cb5d23b1412879a71cb0e7c4975e3b444a8d3a51 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Thu, 8 Oct 2026 08:48:20 +0000 Subject: [PATCH 19/19] defconfig: x86_64: Switch to DDI images Until we have a Barebox release which includes the upstream LVM support, build from a GIT tag on the kernelkit branch. --- configs/x86_64_defconfig | 22 ++++++++++------------ configs/x86_64_minimal_defconfig | 22 ++++++++++------------ external.mk | 5 +++++ patches/barebox/git.hash | 1 + 4 files changed, 26 insertions(+), 24 deletions(-) create mode 100644 patches/barebox/git.hash diff --git a/configs/x86_64_defconfig b/configs/x86_64_defconfig index 9ba804bab..953427fb2 100644 --- a/configs/x86_64_defconfig +++ b/configs/x86_64_defconfig @@ -126,21 +126,20 @@ BR2_PACKAGE_LESS=y BR2_PACKAGE_MG=y BR2_PACKAGE_NANO=y # BR2_TARGET_ROOTFS_TAR is not set -BR2_TARGET_EDK2=y -BR2_TARGET_GRUB2=y -BR2_TARGET_GRUB2_X86_64_EFI=y -BR2_TARGET_GRUB2_BUILTIN_MODULES_EFI="boot linux ext2 squash4 part_gpt normal efi_gop configfile loadenv test echo reboot net efinet tftp loopback cat search" -BR2_TARGET_GRUB2_BUILTIN_CONFIG_EFI="${BR2_EXTERNAL_INFIX_PATH}/board/x86_64/grub-embed.cfg" -BR2_TARGET_GRUB2_INSTALL_TOOLS=y +BR2_TARGET_BAREBOX=y +BR2_TARGET_BAREBOX_CUSTOM_GIT=y +BR2_TARGET_BAREBOX_CUSTOM_GIT_REPO_URL="https://github.com/kernelkit/barebox.git" +BR2_TARGET_BAREBOX_CUSTOM_GIT_VERSION="kkit-next-ddi1" +BR2_TARGET_BAREBOX_USE_CUSTOM_CONFIG=y +BR2_TARGET_BAREBOX_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/x86_64/barebox_defconfig" +BR2_TARGET_BAREBOX_IMAGE_FILE="barebox.efi" +BR2_TARGET_BAREBOX_CUSTOM_EMBEDDED_ENV_PATH="${BUILD_DIR}/infix-bareboxenv" BR2_PACKAGE_HOST_DOSFSTOOLS=y BR2_PACKAGE_HOST_E2FSPROGS=y BR2_PACKAGE_HOST_ENVIRONMENT_SETUP=y BR2_PACKAGE_HOST_GENEXT2FS=y BR2_PACKAGE_HOST_GO_BIN=y BR2_PACKAGE_HOST_MTOOLS=y -BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SUPPORT=y -BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SIGNATURE_SUPPORT=y -BR2_PACKAGE_HOST_UBOOT_TOOLS_FDT_ADD_PUBKEY=y IX_VENDOR_HOME="https://www.kernelkit.org" IX_DESC="Infix is an immutable, friendly, and secure operating system that turns any ARM or x86 device into a powerful, manageable network appliance. Deploy on anything from $35 Raspberry Pi boards to enterprise switches as routers, IoT gateways, or edge devices. Infix models Linux networking features using YANG so you can manage your devices using NETCONF/RESTCONF APIs and focus on your business logic running in isolated containers." IX_HOME="https://github.com/kernelkit/infix/" @@ -189,9 +188,8 @@ BR2_PACKAGE_WEBUI=y BR2_PACKAGE_RAUC_INSTALLATION_STATUS=y BR2_PACKAGE_HOST_PYTHON_YANGDOC=y BR2_PACKAGE_PCIUTILS=y -IX_IMAGE_ITB_AUX=y -IX_IMAGE_ITB_QCOW=y -IX_IMAGE_ITB_RAUC=y +IX_IMAGE_DDI_DISK=y +IX_IMAGE_DDI_RAUC=y IX_IMAGE_README=y IX_TRUSTED_KEYS=y IX_TRUSTED_KEYS_DEVELOPMENT=y diff --git a/configs/x86_64_minimal_defconfig b/configs/x86_64_minimal_defconfig index 92011514f..0276dde74 100644 --- a/configs/x86_64_minimal_defconfig +++ b/configs/x86_64_minimal_defconfig @@ -103,20 +103,19 @@ BR2_PACKAGE_LESS=y BR2_PACKAGE_MG=y BR2_PACKAGE_NANO=y # BR2_TARGET_ROOTFS_TAR is not set -BR2_TARGET_EDK2=y -BR2_TARGET_GRUB2=y -BR2_TARGET_GRUB2_X86_64_EFI=y -BR2_TARGET_GRUB2_BUILTIN_MODULES_EFI="boot linux ext2 squash4 part_gpt normal efi_gop configfile loadenv test echo reboot net efinet tftp loopback cat search" -BR2_TARGET_GRUB2_BUILTIN_CONFIG_EFI="${BR2_EXTERNAL_INFIX_PATH}/board/x86_64/grub-embed.cfg" -BR2_TARGET_GRUB2_INSTALL_TOOLS=y +BR2_TARGET_BAREBOX=y +BR2_TARGET_BAREBOX_CUSTOM_GIT=y +BR2_TARGET_BAREBOX_CUSTOM_GIT_REPO_URL="https://github.com/kernelkit/barebox.git" +BR2_TARGET_BAREBOX_CUSTOM_GIT_VERSION="kkit-next-ddi1" +BR2_TARGET_BAREBOX_USE_CUSTOM_CONFIG=y +BR2_TARGET_BAREBOX_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/x86_64/barebox_defconfig" +BR2_TARGET_BAREBOX_IMAGE_FILE="barebox.efi" +BR2_TARGET_BAREBOX_CUSTOM_EMBEDDED_ENV_PATH="${BUILD_DIR}/infix-bareboxenv" BR2_PACKAGE_HOST_DOSFSTOOLS=y BR2_PACKAGE_HOST_E2FSPROGS=y BR2_PACKAGE_HOST_ENVIRONMENT_SETUP=y BR2_PACKAGE_HOST_GENEXT2FS=y BR2_PACKAGE_HOST_MTOOLS=y -BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SUPPORT=y -BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SIGNATURE_SUPPORT=y -BR2_PACKAGE_HOST_UBOOT_TOOLS_FDT_ADD_PUBKEY=y IX_VENDOR_HOME="https://www.kernelkit.org" IX_DESC="Infix is an immutable, friendly, and secure operating system that turns any ARM or x86 device into a powerful, manageable network appliance. Deploy on anything from $35 Raspberry Pi boards to enterprise switches as routers, IoT gateways, or edge devices. Infix models Linux networking features using YANG so you can manage your devices using NETCONF/RESTCONF APIs and focus on your business logic running in isolated containers." IX_HOME="https://github.com/kernelkit/infix/" @@ -148,9 +147,8 @@ BR2_PACKAGE_MDNS_ALIAS=y BR2_PACKAGE_ONIEPROM=y BR2_PACKAGE_ROUSETTE=y BR2_PACKAGE_RAUC_INSTALLATION_STATUS=y -IX_IMAGE_ITB_AUX=y -IX_IMAGE_ITB_QCOW=y -IX_IMAGE_ITB_RAUC=y +IX_IMAGE_DDI_DISK=y +IX_IMAGE_DDI_RAUC=y IX_IMAGE_README=y IX_TRUSTED_KEYS=y IX_TRUSTED_KEYS_DEVELOPMENT=y diff --git a/external.mk b/external.mk index 5609bd270..ee3f41311 100644 --- a/external.mk +++ b/external.mk @@ -8,6 +8,11 @@ local.mk: @$(call IXMSG,"Installing local override for certain packages") @(cd $O && ln -s $(BR2_EXTERNAL_INFIX_PATH)/local.mk .) +# TEMPORARY +# Until the lvm series gets into a released version and we can create +# a proper release from kernelkit/barebox, we build from a GIT tag. +BAREBOX_HASH_FILES += $(BR2_EXTERNAL_INFIX_PATH)/patches/barebox/git.hash + # # Buildroot package extensions # diff --git a/patches/barebox/git.hash b/patches/barebox/git.hash new file mode 100644 index 000000000..92f7c3fcb --- /dev/null +++ b/patches/barebox/git.hash @@ -0,0 +1 @@ +sha256 b11e120c0b9d08900bdab1931e9c18f849ed76f262db6fd66f819e3312c7637d barebox-kkit-next-ddi1-git4.tar.gz