From a8d1f395f9daf1576ef5e5e08133937d8c40ee40 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 28 Oct 2026 18:34:08 +0000 Subject: [PATCH 01/21] genimage: support GROWFS flag on GPT partitions Infix will use this flag as an indication to expand the partition and filesystem on first boot. --- ...rotective-first-option-for-hybrid-M.patch} | 33 +++---- ...upport-GROWFS-flag-on-GPT-partitions.patch | 94 +++++++++++++++++++ 2 files changed, 111 insertions(+), 16 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/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 2714e611f8..3d24ca38db 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 0000000000..32173632de --- /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 6f52facf6d025b60f30925c29141717f40d39a29 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Mon, 12 Oct 2026 07:47:03 +0000 Subject: [PATCH 02/21] rauc: Add option to download bundles to locations other than /tmp Instead of continuously having to increase the RAM of test VMs as the bundle size grows, download it to disk. --- ...src-main-add-optional-syslog-support.patch | 3 +- ...readiness-once-the-bus-name-is-owned.patch | 3 +- ...ownload-directory-option-for-bundle-.patch | 138 ++++++++++++++++++ 3 files changed, 142 insertions(+), 2 deletions(-) create mode 100644 patches/rauc/1.13/0003-src-bundle-add-download-directory-option-for-bundle-.patch diff --git a/patches/rauc/1.13/0001-src-main-add-optional-syslog-support.patch b/patches/rauc/1.13/0001-src-main-add-optional-syslog-support.patch index ef9ae80a67..b236b9dd32 100644 --- a/patches/rauc/1.13/0001-src-main-add-optional-syslog-support.patch +++ b/patches/rauc/1.13/0001-src-main-add-optional-syslog-support.patch @@ -1,7 +1,7 @@ From 28341bdaaa99fc9af4df0f74626a33b7da82a3d7 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Thu, 23 Nov 2023 18:49:36 +0100 -Subject: [PATCH 1/2] src/main: add optional syslog support +Subject: [PATCH 1/3] src/main: add optional syslog support Organization: Wires This patch adds an optional syslog backend alternative to the default @@ -14,6 +14,7 @@ optional argument is given, e.g., `--syslog=local0`. Useful for basic message filtering to a dedicated log file. Signed-off-by: Joachim Wiberg +Signed-off-by: Tobias Waldekranz --- docs/reference.rst | 1 + src/main.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/patches/rauc/1.13/0002-service-notify-readiness-once-the-bus-name-is-owned.patch b/patches/rauc/1.13/0002-service-notify-readiness-once-the-bus-name-is-owned.patch index cde54dfd63..93c5a4f074 100644 --- a/patches/rauc/1.13/0002-service-notify-readiness-once-the-bus-name-is-owned.patch +++ b/patches/rauc/1.13/0002-service-notify-readiness-once-the-bus-name-is-owned.patch @@ -1,7 +1,7 @@ From 4ae5297fed47fd7b499e656a6e0af37683632975 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Thu, 1 Oct 2026 11:20:00 +0200 -Subject: [PATCH 2/2] service: notify readiness once the bus name is owned +Subject: [PATCH 2/3] service: notify readiness once the bus name is owned Organization: Wires A supervisor only sees the process start, not when the service can take @@ -12,6 +12,7 @@ Send READY=1 on NOTIFY_SOCKET, the sd_notify(3) protocol, when the name has been acquired, so a supervisor can hold back clients. Signed-off-by: Joachim Wiberg +Signed-off-by: Tobias Waldekranz --- src/service.c | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/patches/rauc/1.13/0003-src-bundle-add-download-directory-option-for-bundle-.patch b/patches/rauc/1.13/0003-src-bundle-add-download-directory-option-for-bundle-.patch new file mode 100644 index 0000000000..2183e3924d --- /dev/null +++ b/patches/rauc/1.13/0003-src-bundle-add-download-directory-option-for-bundle-.patch @@ -0,0 +1,138 @@ +From f2aef5c0fdd44bcbd896b6ac1114a2ad567bcbf4 Mon Sep 17 00:00:00 2001 +From: Tobias Waldekranz +Date: Mon, 12 Oct 2026 07:27:09 +0000 +Subject: [PATCH 3/3] src/bundle: add download-directory option for bundle + downloads +Organization: Wires + +On boards where /tmp is too small to hold a bundle, but disk is +available, allow bundles to be downloaded to a user-configurable +location instead. + +Therefore, add a 'download-directory' option to system.conf which will +be used instead of the system default temporary directory, when set. + +Signed-off-by: Tobias Waldekranz +Signed-off-by: Tobias Waldekranz +--- + docs/reference.rst | 10 ++++++++++ + include/config_file.h | 2 ++ + src/bundle.c | 24 +++++++++++++++++++----- + src/config_file.c | 4 ++++ + test/config_file.c | 2 ++ + 5 files changed, 37 insertions(+), 5 deletions(-) + +diff --git a/docs/reference.rst b/docs/reference.rst +index 5504661c..88d5ae28 100644 +--- a/docs/reference.rst ++++ b/docs/reference.rst +@@ -212,6 +212,16 @@ Example configuration: + If RAUC is configured with streaming support, this has no effect, as the + bundle is not downloaded as a whole. + ++``download-directory`` (optional) ++ Directory in which RAUC creates a temporary subdirectory to store ++ downloaded bundles. ++ Defaults to the system's temporary directory (``$TMPDIR`` or ``/tmp``). ++ Use this if the default location is too small to hold a full bundle ++ (see ``max-bundle-download-size``). ++ The directory must already exist. ++ If RAUC is configured with streaming support, this has no effect, as the ++ bundle is not downloaded as a whole. ++ + ``variant-name`` (optional) + String to be used as variant name for this board. + If set, neither ``variant-file`` nor ``variant-dtb`` must be set. +diff --git a/include/config_file.h b/include/config_file.h +index ecb962c2..8312d4ce 100644 +--- a/include/config_file.h ++++ b/include/config_file.h +@@ -51,6 +51,8 @@ typedef struct { + gboolean efi_use_bootnext; + /* maximum filesize to download in bytes */ + guint64 max_bundle_download_size; ++ /* directory in which to place downloaded bundles (NULL: use default tmp dir) */ ++ gchar *download_directory; + /* path prefix where rauc may create mount directories */ + gchar *mount_prefix; + gchar *store_path; +diff --git a/src/bundle.c b/src/bundle.c +index 7a27f6b4..ea7c92b5 100644 +--- a/src/bundle.c ++++ b/src/bundle.c +@@ -2249,11 +2249,25 @@ gboolean check_bundle(const gchar *bundlename, RaucBundle **bundle, CheckBundleP + goto out; + } + #elif ENABLE_NETWORK +- g_autofree gchar *tmpdir = g_dir_make_tmp("rauc-XXXXXX", &ierror); +- if (tmpdir == NULL) { +- g_propagate_prefixed_error(error, ierror, "Failed to create tmp dir: "); +- res = FALSE; +- goto out; ++ g_autofree gchar *tmpdir = NULL; ++ ++ if (r_context()->config->download_directory) { ++ tmpdir = g_build_filename(r_context()->config->download_directory, "rauc-XXXXXX", NULL); ++ if (g_mkdtemp(tmpdir) == NULL) { ++ int err = errno; ++ g_set_error(error, G_FILE_ERROR, g_file_error_from_errno(err), ++ "Failed to create tmp dir in %s: %s", ++ r_context()->config->download_directory, g_strerror(err)); ++ res = FALSE; ++ goto out; ++ } ++ } else { ++ tmpdir = g_dir_make_tmp("rauc-XXXXXX", &ierror); ++ if (tmpdir == NULL) { ++ g_propagate_prefixed_error(error, ierror, "Failed to create tmp dir: "); ++ res = FALSE; ++ goto out; ++ } + } + + ibundle->origpath = g_strdup(bundlename); +diff --git a/src/config_file.c b/src/config_file.c +index 03f7d20a..1be569b8 100644 +--- a/src/config_file.c ++++ b/src/config_file.c +@@ -903,6 +903,9 @@ gboolean load_config(const gchar *filename, RaucConfig **config, GError **error) + } + g_key_file_remove_key(key_file, "system", "max-bundle-download-size", NULL); + ++ c->download_directory = resolve_path_take(filename, ++ key_file_consume_string(key_file, "system", "download-directory", NULL)); ++ + c->mount_prefix = key_file_consume_string(key_file, "system", "mountprefix", NULL); + if (!c->mount_prefix) { + g_debug("No mount prefix provided, using /mnt/rauc/ as default"); +@@ -1274,6 +1277,7 @@ void free_config(RaucConfig *config) + g_free(config->system_bootloader); + g_free(config->system_bb_statename); + g_free(config->system_bb_dtbpath); ++ g_free(config->download_directory); + g_free(config->mount_prefix); + g_free(config->store_path); + g_free(config->tmp_path); +diff --git a/test/config_file.c b/test/config_file.c +index d6b0be43..9a3df7d1 100644 +--- a/test/config_file.c ++++ b/test/config_file.c +@@ -50,6 +50,7 @@ bootloader=barebox\n\ + mountprefix=/mnt/myrauc/\n\ + statusfile=/mnt/persistent-rw-fs/system.raucs\n\ + max-bundle-download-size=42\n\ ++download-directory=/data/tmp\n\ + bundle-formats=verity\n\ + \n\ + [keyring]\n\ +@@ -118,6 +119,7 @@ type=trees\n\ + g_assert_true(config->activate_installed); + g_assert_cmpstr(config->statusfile_path, ==, "/mnt/persistent-rw-fs/system.raucs"); + g_assert_cmpint(config->max_bundle_download_size, ==, 42); ++ g_assert_cmpstr(config->download_directory, ==, "/data/tmp"); + g_assert_cmphex(config->bundle_formats_mask, ==, 0x2); + g_assert_cmpstr(config->store_path, ==, "/var/lib/default.castr/"); + g_assert_cmpstr(config->tmp_path, ==, "/tmp/"); +-- +2.43.0 + From c00505ba0a239b58ad7539a53fe4cff9c1a40169 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Thu, 26 Feb 2026 20:00:07 +0000 Subject: [PATCH 03/21] 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 411a696207..8ac7caf38e 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 58a881a25c0634a90c17d1b52a69be6433da6cf9 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 6 Oct 2026 08:29:13 +0000 Subject: [PATCH 04/21] 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 b02d90f8ef..33aca3eb33 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 56212fdbca3718272aa35a9ab56e0bb1af017b9b Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 7 Oct 2026 10:24:22 +0000 Subject: [PATCH 05/21] 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 e10c9b19be..bec6a5bed2 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 0c03da7d61be0a503bada453e5522a3405ccb39e Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Sun, 1 Mar 2026 18:27:29 +0000 Subject: [PATCH 06/21] 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 12587b2288..e5ce369095 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 2b65ea334fa5b9006b9ffbf16991be6829b8902b Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Sun, 1 Mar 2026 18:30:12 +0000 Subject: [PATCH 07/21] 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 592700c79c..9e496e2126 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 2d77c975581190b32fa7e3f815f025b13a126bb0 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 7 Oct 2026 10:23:12 +0000 Subject: [PATCH 08/21] 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 e8597e150bbfcaa5d6f48cd01a69f7bd43737a91 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 3 Mar 2026 07:32:40 +0000 Subject: [PATCH 09/21] 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 f81d614513..079836db98 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 0000000000..b19280a811 --- /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 0000000000..641a199fdb --- /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 0000000000..d83079a9e8 --- /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 0000000000..6b8c2fc52a --- /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 0000000000..08bf6cd8eb --- /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 e0586379db..a570f09d47 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 0000000000..af12ba9d6e --- /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 0000000000..acec489108 --- /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 0000000000..758448ef14 --- /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 0000000000..b4867734ca --- /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 cb7bd6b3e1..1dc0e6f6a5 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 0ed7469f96414c94e5217908fd7265330260b690 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Thu, 4 Dec 2025 08:01:18 +0000 Subject: [PATCH 10/21] 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 | 19 ++ .../image-ddi/rootfs/etc/tmpfiles.d/rauc.conf | 3 + .../image/image-ddi/rootfs/mnt/rw/.empty | 0 .../image-ddi/rootfs/usr/libexec/infix/mnt | 253 ++++++++++++++++++ .../rootfs/usr/libexec/infix/rauc-config-gen | 89 ++++++ 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 +- 20 files changed, 570 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/etc/tmpfiles.d/rauc.conf 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 079836db98..e82b5fd457 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 0000000000..75b6a1c945 --- /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 0000000000..13d0e9723d --- /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 0000000000..bd51942f30 --- /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 0000000000..e69de29bb2 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 0000000000..e69de29bb2 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 0000000000..ac2c5cdbe6 --- /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 0000000000..d79a3eac48 --- /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 0000000000..2bdda4b624 --- /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 0000000000..124198fa0a --- /dev/null +++ b/board/common/image/image-ddi/rootfs/etc/fstab @@ -0,0 +1,19 @@ +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 +/boot/efi /mnt/aux none defaults,bind 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/etc/tmpfiles.d/rauc.conf b/board/common/image/image-ddi/rootfs/etc/tmpfiles.d/rauc.conf new file mode 100644 index 0000000000..a8ec0842c8 --- /dev/null +++ b/board/common/image/image-ddi/rootfs/etc/tmpfiles.d/rauc.conf @@ -0,0 +1,3 @@ +d /var/lib/rauc 0755 root root +d /var/lib/rauc/dl 0755 root root +d /var/lib/rauc/mnt 0755 root root 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 0000000000..e69de29bb2 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 0000000000..7bec8c9159 --- /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 0000000000..137f73d15d --- /dev/null +++ b/board/common/image/image-ddi/rootfs/usr/libexec/infix/rauc-config-gen @@ -0,0 +1,89 @@ +#!/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 e5ce369095..d026d1c404 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 5a94fe885d..4d4a709e00 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 0000000000..cd19524a7c --- /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 071cfee476..5609bd2704 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 1d76d34680..bc2b6a683b 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 33aca3eb33..4259f1cb84 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 655b73d4c864047488dd8fc08d224f509e544af8 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Thu, 18 Dec 2025 00:18:42 +0000 Subject: [PATCH 11/21] 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 e82b5fd457..3f28073ca2 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 0000000000..7b1f60b493 --- /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 0000000000..a4e5966c13 --- /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 0000000000..e666e0238e --- /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 c7e162be77..92be42fef1 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 0000000000..14fd2d8126 --- /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 4f730597bf00a0d3e1b70cb92a97b6d7300e90f9 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 7 Oct 2026 14:59:44 +0000 Subject: [PATCH 12/21] 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 3f28073ca2..1ccee1a14a 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 0000000000..6389bf363c --- /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 0000000000..1caf05118b --- /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 0000000000..942b050b60 --- /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 4d44ccad382e0fb866e39f2452e127d1da34e888 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Thu, 26 Feb 2026 14:25:30 +0000 Subject: [PATCH 13/21] 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 1ccee1a14a..60a3b92d13 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 0000000000..48a31f6357 --- /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 0000000000..5f54f60f82 --- /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 0000000000..b12dfa90e1 --- /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 27f8fefb72ba36cc5f01d75e317f5c2adea3ea05 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 29 Sep 2026 11:21:54 +0000 Subject: [PATCH 14/21] 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 60a3b92d13..48e0181fa0 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 a570f09d47..af6b73f749 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 be732a52f37bd34ea4835ae03e302d692994c261 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Thu, 26 Feb 2026 12:32:46 +0000 Subject: [PATCH 15/21] 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 0000000000..39f795e074 --- /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 93ac6fdeba8b6c4b45df2a82fd2c00858e8654ad Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Wed, 4 Feb 2026 18:37:20 +0000 Subject: [PATCH 16/21] 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 af6b73f749..fd31bfb01c 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 75b6a1c945..42a80abe66 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 0000000000..126b38d55c --- /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 0000000000..1a033464a3 --- /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 0000000000..790e9d6b9c --- /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 0000000000..56a1746a82 --- /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 0000000000..011a90fb70 --- /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 f0e0627142..0d5a47032e 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 ae84875bbcb02e215f05d7dc0a356ef0ff013aed Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Fri, 27 Feb 2026 23:18:09 +0000 Subject: [PATCH 17/21] 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 0000000000..79a35f28f8 --- /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 0000000000..4f57ae7d8e --- /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 bdee41a053..694c38d6b2 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 887f322094c1647179e7e2ea77f2a625bd6b6dd2 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Thu, 8 Oct 2026 09:29:46 +0000 Subject: [PATCH 18/21] {conf,yanger}d: Add Barebox support Add a glue script to manage the priority-based approach used by the Barebox bootchooser to determine the boot order. --- src/confd/bin/Makefile.am | 4 +- src/confd/bin/bareboxcfg | 68 +++++++++++++++++++ src/confd/src/system-software.c | 6 +- src/yangerd/cmd/yangerd/main.go | 26 ++++--- src/yangerd/internal/collector/boot.go | 32 ++++++--- src/yangerd/internal/collector/boot_test.go | 59 ++++++++++++++-- .../internal/fswatcher/fswatcher_test.go | 8 +-- .../+usr+libexec+confd+bareboxcfg_--get-order | 1 + 8 files changed, 173 insertions(+), 31 deletions(-) create mode 100755 src/confd/bin/bareboxcfg create mode 100644 test/case/statd/system/system/run/+usr+libexec+confd+bareboxcfg_--get-order diff --git a/src/confd/bin/Makefile.am b/src/confd/bin/Makefile.am index 4b2ad2767f..51975838f5 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 0000000000..20a1112458 --- /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/src/system-software.c b/src/confd/src/system-software.c index 1db60ea455..eababfb83d 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/yangerd/cmd/yangerd/main.go b/src/yangerd/cmd/yangerd/main.go index 2f31de2d1a..9a6e74b672 100644 --- a/src/yangerd/cmd/yangerd/main.go +++ b/src/yangerd/cmd/yangerd/main.go @@ -7,6 +7,7 @@ import ( "log/slog" "os" "os/signal" + "path/filepath" "strconv" "strings" "sync" @@ -366,15 +367,22 @@ func main() { Debounce: 200 * time.Millisecond, UseMerge: true, } - // Watch the parent directory, not the file: fw_setenv (U-Boot) and - // grub-editenv may rewrite the env via a temp file + rename, which - // gives it a new inode that a direct file watch never sees. Watching - // the directory catches the Create/Rename (and still catches in-place - // writes), so a boot-order change after a RAUC install is reflected - // without waiting for a reboot. - for _, path := range []string{"/mnt/aux/grub/grubenv", "/mnt/aux/uboot.env"} { - if err := fsw.WatchSymlink(path, bootOrderHandler); err != nil { - slogLog.Debug("fswatcher boot-order watch skipped", "path", path, "err", err) + // Watch the parent directory, not the file: fw_setenv (U-Boot) may + // rewrite the env via a temp file + rename, which gives it a new + // inode that a direct file watch never sees. Watching the directory + // catches the Create/Rename (and still catches in-place writes), so a + // boot-order change after a RAUC install is reflected without waiting + // for a reboot. + if err := fsw.WatchSymlink("/mnt/aux/uboot.env", bootOrderHandler); err != nil { + slogLog.Debug("fswatcher boot-order watch skipped", "path", "/mnt/aux/uboot.env", "err", err) + } + // barebox-state writes the partition in place through its device + // node, so watch the node the by-partuuid link resolves to. + if link := collector.BareboxStateDevice(fs); link != "" { + if dev, err := filepath.EvalSymlinks(link); err != nil { + slogLog.Debug("fswatcher boot-order watch skipped", "path", link, "err", err) + } else if err := fsw.Watch(dev, bootOrderHandler); err != nil { + slogLog.Warn("fswatcher watch failed", "path", dev, "err", err) } } dnsHandler := fswatcher.WatchHandler{ diff --git a/src/yangerd/internal/collector/boot.go b/src/yangerd/internal/collector/boot.go index 0d18be6c62..bd7ea69aec 100644 --- a/src/yangerd/internal/collector/boot.go +++ b/src/yangerd/internal/collector/boot.go @@ -42,6 +42,9 @@ func BootSoftware(ctx context.Context, cmd CommandRunner) json.RawMessage { software[key] = v } } + if booted, ok := software["booted"].(string); ok { + software["booted"] = strings.TrimPrefix(booted, "internal-") + } if slots := softwareSlots(raucData); slots != nil { software["slot"] = slots } @@ -69,21 +72,31 @@ func ReadBootOrder(ctx context.Context, cmd CommandRunner) []string { } } - out, err = cmd.Run(ctx, "grub-editenv", "/mnt/aux/grub/grubenv", "list") + out, err = cmd.Run(ctx, "/usr/libexec/confd/bareboxcfg", "--get-order") if err == nil { - for _, line := range strings.Split(string(out), "\n") { - if strings.Contains(line, "ORDER") { - parts := strings.SplitN(line, "=", 2) - if len(parts) == 2 { - return strings.Fields(strings.TrimSpace(parts[1])) - } - } + if order := strings.Fields(string(out)); len(order) > 0 { + return order } } return nil } +// BareboxStateDevice is the by-partuuid link to the barebox-state +// partition, empty when /etc/partition-uuid does not name one. +func BareboxStateDevice(fs FileReader) string { + data, err := fs.ReadFile("/etc/partition-uuid") + if err != nil { + return "" + } + for _, line := range strings.Split(string(data), "\n") { + if uuid, ok := strings.CutPrefix(line, "BAREBOX_STATE_UUID="); ok { + return "/dev/disk/by-partuuid/" + strings.Trim(uuid, "\"") + } + } + return "" +} + // softwareSlots lists the RAUC slots, nil when rauc reported none. func softwareSlots(raucData map[string]interface{}) []interface{} { slotsArr, ok := raucData["slots"].([]interface{}) @@ -113,6 +126,9 @@ func softwareSlot(name string, val map[string]interface{}) map[string]interface{ "class": val["class"], "state": val["state"], } + if bootname, ok := val["bootname"].(string); ok { + s["bootname"] = strings.TrimPrefix(bootname, "internal-") + } slotStatus, _ := val["slot_status"].(map[string]interface{}) if slotStatus == nil { diff --git a/src/yangerd/internal/collector/boot_test.go b/src/yangerd/internal/collector/boot_test.go index 9b53c0bf20..e74c1884c2 100644 --- a/src/yangerd/internal/collector/boot_test.go +++ b/src/yangerd/internal/collector/boot_test.go @@ -173,7 +173,7 @@ func TestBootSoftwareAllCommandsFail(t *testing.T) { Errors: map[string]error{ "rauc status --detailed --output-format=json": fmt.Errorf("not found"), "fw_printenv BOOT_ORDER": fmt.Errorf("not found"), - "grub-editenv /mnt/aux/grub/grubenv list": fmt.Errorf("not found"), + "/usr/libexec/confd/bareboxcfg --get-order": fmt.Errorf("not found"), }, } @@ -204,10 +204,10 @@ func TestReadBootOrderFwPrintenv(t *testing.T) { } } -func TestReadBootOrderGrubFallback(t *testing.T) { +func TestReadBootOrderBareboxFallback(t *testing.T) { runner := &testutil.MockRunner{ Results: map[string][]byte{ - "grub-editenv /mnt/aux/grub/grubenv list": []byte("ORDER=B A\n"), + "/usr/libexec/confd/bareboxcfg --get-order": []byte("B A\n"), }, Errors: map[string]error{ "fw_printenv BOOT_ORDER": fmt.Errorf("command not found"), @@ -224,8 +224,8 @@ func TestReadBootOrderBothFail(t *testing.T) { runner := &testutil.MockRunner{ Results: map[string][]byte{}, Errors: map[string]error{ - "fw_printenv BOOT_ORDER": fmt.Errorf("not found"), - "grub-editenv /mnt/aux/grub/grubenv list": fmt.Errorf("not found"), + "fw_printenv BOOT_ORDER": fmt.Errorf("not found"), + "/usr/libexec/confd/bareboxcfg --get-order": fmt.Errorf("not found"), }, } @@ -234,3 +234,52 @@ func TestReadBootOrderBothFail(t *testing.T) { t.Fatalf("expected nil, got %v", order) } } + +func TestBootSoftwareBareboxBootname(t *testing.T) { + runner := &testutil.MockRunner{ + Results: map[string][]byte{ + "rauc status --detailed --output-format=json": []byte(`{ + "booted": "internal-primary", + "slots": [{"rootfs.0": {"bootname": "internal-primary", "class": "rootfs", "state": "booted"}}] +}`), + "/usr/libexec/confd/bareboxcfg --get-order": []byte("primary secondary\n"), + }, + Errors: map[string]error{ + "fw_printenv BOOT_ORDER": fmt.Errorf("not found"), + }, + } + + var result map[string]map[string]interface{} + json.Unmarshal(BootSoftware(context.Background(), runner), &result) + sw := result["infix-system:software"] + + if sw["booted"] != "primary" { + t.Fatalf("booted: expected 'primary', got %v", sw["booted"]) + } + slot := sw["slot"].([]interface{})[0].(map[string]interface{}) + if slot["bootname"] != "primary" { + t.Fatalf("bootname: expected 'primary', got %v", slot["bootname"]) + } +} + +func TestBareboxStateDevice(t *testing.T) { + fs := &testutil.MockFileReader{ + Files: map[string][]byte{ + "/etc/partition-uuid": []byte("AUX_UUID=\"78460f84-de84-4fe7-89bd-4c1f433b2230\"\n" + + "BAREBOX_STATE_UUID=\"80ca5554-c020-11f1-ba4d-b3c49d5dbf47\"\n"), + }, + } + + dev := BareboxStateDevice(fs) + if dev != "/dev/disk/by-partuuid/80ca5554-c020-11f1-ba4d-b3c49d5dbf47" { + t.Fatalf("unexpected device %q", dev) + } +} + +func TestBareboxStateDeviceMissing(t *testing.T) { + fs := &testutil.MockFileReader{Files: map[string][]byte{}} + + if dev := BareboxStateDevice(fs); dev != "" { + t.Fatalf("expected empty, got %q", dev) + } +} diff --git a/src/yangerd/internal/fswatcher/fswatcher_test.go b/src/yangerd/internal/fswatcher/fswatcher_test.go index 99f1e96fe2..0aeeadb8d2 100644 --- a/src/yangerd/internal/fswatcher/fswatcher_test.go +++ b/src/yangerd/internal/fswatcher/fswatcher_test.go @@ -589,10 +589,10 @@ func TestWatchSymlinkReplace(t *testing.T) { cancel() } -// fw_setenv (U-Boot) and grub-editenv rewrite the env via a temp file + -// atomic rename, so the env gets a new inode. A direct file watch misses -// that; the parent-directory watch used for boot-order must catch it, -// otherwise operational boot-order stays stale until the next reboot. +// fw_setenv (U-Boot) rewrites the env via a temp file + atomic rename, +// so the env gets a new inode. A direct file watch misses that; the +// parent-directory watch used for boot-order must catch it, otherwise +// operational boot-order stays stale until the next reboot. func TestWatchSymlinkAtomicRename(t *testing.T) { fw, tr := newTestFSWatcher(t) diff --git a/test/case/statd/system/system/run/+usr+libexec+confd+bareboxcfg_--get-order b/test/case/statd/system/system/run/+usr+libexec+confd+bareboxcfg_--get-order new file mode 100644 index 0000000000..363e8d2ade --- /dev/null +++ b/test/case/statd/system/system/run/+usr+libexec+confd+bareboxcfg_--get-order @@ -0,0 +1 @@ +primary secondary net From 9ea50ded81ac86b237e05069272872107296a573 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Thu, 8 Oct 2026 11:56:03 +0000 Subject: [PATCH 19/21] test: Use DDI images for qeneth testing --- test/env | 16 +--- test/inject-test-mode | 108 ++++++++++++---------- test/templates/infix-bios-x86_64.mustache | 6 +- test/test.mk | 3 +- 4 files changed, 68 insertions(+), 65 deletions(-) diff --git a/test/env b/test/env index af1b9c7cef..21bcda80a6 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 2f499b5b57..138eea5827 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 2fe4ffa512..0dc6683620 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}} @@ -21,7 +21,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 e292f12a80..5eb8e3256b 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 d0f571883d161f4326faf2d4382503213d535d0b Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Thu, 8 Oct 2026 08:48:20 +0000 Subject: [PATCH 20/21] 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 d11a17f550..33f58df525 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="v2026.10.0-kkit1" +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 c7a23b19ef..94f4bf72ba 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="v2026.10.0-kkit1" +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 5609bd2704..ee3f413113 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 0000000000..f77a414650 --- /dev/null +++ b/patches/barebox/git.hash @@ -0,0 +1 @@ +sha256 9d6d4e645d229c6c62dc30c2fd2b4cfbd02b07bd13d24cd701332d2cc65cc6b2 barebox-v2026.10.0-kkit1-git4.tar.gz From a34f99bb20ffd4e5d3eddaee2c3cb51a1769047a Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Thu, 29 Oct 2026 13:46:47 +0000 Subject: [PATCH 21/21] doc: Update docs and changelog with info about DDI images --- doc/ChangeLog.md | 23 ++++ doc/boot.md | 282 ++++++++++++++++++++++++++++++++++++++--------- doc/netboot.md | 87 ++++++++++++--- 3 files changed, 326 insertions(+), 66 deletions(-) diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index c43a6bd79a..7458ddb53e 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -6,6 +6,18 @@ All notable changes to the project are documented in this file. [v26.10.0][UNRELEASED] ------------------------- +> [!IMPORTANT] +> This release includes **breaking changes** for x86_64 systems. The +> bootloader and disk layout are new, see [Boot Procedure][boot], so an +> existing x86_64 installation cannot be upgraded with a bundle: +> +> - Back up your `startup-config.cfg` +> - Reinstall from the new disk image, `infix-x86_64.disk`, or replace +> the disk of a virtual machine with `infix-x86_64.qcow2` +> - Restore your `startup-config.cfg` +> +> Other architectures are not affected. + ### Changes - Upgrade Linux kernel to 6.18.59 (LTS) @@ -163,6 +175,16 @@ All notable changes to the project are documented in this file. learned and static entries, also those a switch learns on its own, with the age of each entry and whether it is offloaded to the switch. The CLI can list one VLAN, one port, or one address, see [FDB][fdb] +- x86_64: GRUB is replaced by Barebox, and the system image is now a + signed Discoverable Disk Image (DDI). With UEFI Secure Boot + enabled, the bootloader, kernel and root filesystem are all verified + before the system starts. The system disk uses LVM: the two system + images and the persistent storage are volumes on one partition, + which is grown to fill the disk on first boot, when the second + system image is also created, see [Boot Procedure][boot]. The disk + layout is new, so existing x86_64 installations must be reinstalled + from the new disk image, `infix-x86_64.disk` or `.qcow2`, they + cannot be upgraded with a bundle ### Fixes @@ -238,6 +260,7 @@ All notable changes to the project are documented in this file. [usb]: https://www.kernelkit.org/infix/latest/hardware/#usb-ports [gps]: https://www.kernelkit.org/infix/latest/hardware/#anchor-a-gps-receiver-to-a-usb-port [fdb]: https://www.kernelkit.org/infix/latest/bridging/#unicast-forwarding-database +[boot]: https://www.kernelkit.org/infix/latest/boot/ [mcast]: https://www.kernelkit.org/infix/latest/bridging/#multicast-filtering-and-snooping [v26.09.0][] - 2026-09-30 diff --git a/doc/boot.md b/doc/boot.md index 49c8a4e4da..e89a3e3821 100644 --- a/doc/boot.md +++ b/doc/boot.md @@ -60,7 +60,7 @@ bootloader's validation procedure, user configuration is kept to a minimum. Two settings are available: **Boot order**: Since Infix maintains two copies of its software image, -and as some bootloaders support [netbooting][2], the order in which boot +and as both bootloaders support [netbooting][2], the order in which boot sources are considered can be configured. To select the active source, use [RAUC][]: @@ -73,10 +73,14 @@ Where `` is one of: | **``** | **Source** | |--------------|---------------------------| -| `rootfs.0` | Primary partition | -| `rootfs.1` | Secondary partition | +| `rootfs.0` | Primary image | +| `rootfs.1` | Secondary image | | `net.0` | Netboot (where supported) | +On U-Boot systems the primary and secondary images are stored in disk +partitions, on Barebox systems they are logical volumes, see [Image +Formats](#image-formats). + **Debug**: By default, the kernel will only output errors to the console during boot. Optionally, this can be altered such that all enabled messages are logged. @@ -84,17 +88,8 @@ enabled messages are logged. On systems using _U-Boot_, this can be enabled by running `fw_setenv DEBUG 1`. To restore the default behavior, run `fw_setenv DEBUG`. -On systems running _GRUB_, this can be enabled by running: - -``` -root@example:~# grub-editenv /mnt/aux/grub/grubenv set DEBUG=1 -``` - -To restore the default behavior, run: - -``` -root@example:~# grub-editenv /mnt/aux/grub/grubenv unset DEBUG -``` +On systems using _Barebox_, the kernel command line is part of the +signed boot entry in the image, so this setting is not available. ### U-Boot @@ -103,8 +98,8 @@ _integrity_ and _authenticity_ of an Infix image. As such, it can be used as a part of a _Secure Boot_ chain, given that the preceding links are able to do the same. -Supports booting Infix from a block device using the [Disk -Image](#disk-image) layout. Currently, Virtio and MMC disks are +Supports booting Infix from a block device using the [ITB Disk +Image](#itb-disk-image) layout. Currently, Virtio and MMC disks are supported. An [FIT Framed Squash Image](#fit-framed-squash-image) can be used to @@ -115,23 +110,53 @@ Access to U-Boot's shell is disabled to prevent side-loading of malicious software. To configure the active boot partition, refer to the [Bootloader Configuration](#configuration) section. -### GRUB - -Used on _x86_64_ based systems. Neither the _integrity_ nor the -_authenticity_ of the Infix image is verified. It is only intended to -provide a way of booting a [Disk Image](#disk-image), such that a -standard [System Upgrade](#system-upgrade) can be performed on -virtualized instances. - -Access to the GRUB shell is not limited in any way, and the boot -partition can be selected interactively at boot using the arrow keys. It -is also possible to permanently configure the default partition from -Infix using the [Bootloader Configuration](#configuration). +### Barebox + +Used on _x86_64_ based systems, where it is started by the system's +UEFI firmware from the [EFI System Partition](#esp-esp-backup-efi-system-partitions). +It boots Infix from a [Discoverable Disk Image](#discoverable-disk-image-ddi) +(DDI), either from one of the logical volumes of the [DDI Disk +Image](#ddi-disk-image), or over the network. + +When UEFI _Secure Boot_ is enabled, Barebox verifies both the +_integrity_ and _authenticity_ of the Infix image before booting it: + +1. The UEFI firmware only starts Barebox if its signature can be + validated by a certificate in the firmware's signature database + (`db`). +1. Barebox validates the signature of the DDI's dm-verity root hash + against the same database. The root hash, in turn, protects every + block of the root filesystem. +1. Barebox validates the signature of the Linux kernel, which carries + its initramfs, before starting it. + +Once Linux is running, the initramfs sets up the dm-verity protected +root filesystem using the root hash passed on by Barebox, such that +any modification of the image is detected when the affected blocks +are read. + +With _Secure Boot_ disabled, e.g., in virtualized instances without +enrolled keys, the same images are booted but none of the signatures +are required to be valid. + +The boot order is managed by Barebox' _bootchooser_, using a set of +priorities and remaining boot attempts for each target, stored in the +[`barebox-state`](#barebox-state-bootloader-state) partition. Each +boot of an image consumes one attempt, and once Infix has successfully +loaded its `startup-config` the image is marked as good, which resets +its attempts to three. An image that fails to boot three times in a +row is skipped in favor of the next one in order. To configure the +active image, refer to the [Bootloader Configuration](#configuration) +section. + +When netbooting, Barebox uses DHCP to configure the network and TFTP +to fetch the DDI named in the DHCP bootfile option. The image is +booted from RAM, using the same validation as when booting from disk. ## System Boot -After the system firmware (BIOS or and [boot loader](boot.md) start -Linux the following happens. The various failure modes, e.g., missing +After the system firmware and [boot loader](#bootloader) start Linux +the following happens. The various failure modes, e.g., missing password in VPD, are detailed later in this section. ![System boot flowchart](img/fail-secure.svg) @@ -230,16 +255,37 @@ root@example:~# rauc install ... ``` -Where the file or URL points to a [RAUC Upgrade Bundle](#rauc-upgrade-bundle). +Where the file or URL points to a RAUC Upgrade Bundle, i.e., a [DDI +RAUC Upgrade Bundle](#ddi-rauc-upgrade-bundle) or an [ITB RAUC Upgrade +Bundle](#itb-rauc-upgrade-bundle), depending on the system. -This will upgrade the partition not currently running. After a +This will upgrade the image not currently running. After a successful upgrade is completed, you can reboot your system, which -will then boot from the newly installed image. Since the partition -from which you were originally running is now inactive, running the -same upgrade command again will bring both partitions into sync. +will then boot from the newly installed image. Since the image from +which you were originally running is now inactive, running the same +upgrade command again will bring both images into sync. ## Image Formats +Infix is built in one of two families of image formats, depending on +the bootloader used by the target: + +- **DDI based**: used with [Barebox](#barebox), currently on _x86_64_. + The root filesystem is distributed as a [Discoverable Disk + Image](#discoverable-disk-image-ddi), and the system disk is managed + using LVM. +- **ITB based**: used with [U-Boot](#u-boot), on _aarch64_. The root + filesystem is distributed as a [FIT Framed Squash + Image](#fit-framed-squash-image), and the system disk uses a fixed + partition layout. + +Common to both is the [SquashFS Image](#squashfs-image), which holds +Infix's Linux kernel and root filesystem. + +In the names below, `${ARTIFACT}` is the image name of the build, +e.g. `infix-x86_64`, with the release version appended for release +builds. + ### SquashFS Image **Canonical Name**: `rootfs.squashfs` @@ -248,10 +294,141 @@ The central read-only filesystem image containing Infix's Linux kernel, device trees, and root filesystem. All other images bundle this image, or is dependent on it, in one way or another. -On its own, it can be used as an [initrd][] to efficiently boot a -virtual instance of Infix. +On ITB based systems, it can be used on its own as an [initrd][] to +efficiently boot a virtual instance of Infix. + +### DDI Based Images + +#### Discoverable Disk Image (DDI) + +**Canonical Name**: `${ARTIFACT}.raw` + +A GPT partitioned image following the [Discoverable Disk Image][DDI] +specification, holding three partitions: + +| **Partition** | **Contents** | +|-------------------|------------------------------------------------------| +| `root` | The [SquashFS Image](#squashfs-image) | +| `root-verity` | The dm-verity hash tree of the SquashFS image | +| `root-verity-sig` | A signature of the hash tree's root hash, as JSON | + +The partition type UUIDs follow the [Discoverable Partitions][DPS] +specification, so that the partitions can be located without any +external metadata. The partition UUIDs of `root` and `root-verity` +are derived from the root hash itself, which lets the bootloader and +the initramfs find the partitions belonging to a particular root hash. -### FIT Framed Squash Image +Since the signature covers the root hash, and the root hash covers +every block of the root filesystem, a single signature check is enough +to establish trust in the entire image. + +The DDI is the unit that is installed on the system disk, carried in +the [DDI RAUC Upgrade Bundle](#ddi-rauc-upgrade-bundle), and used to +[netboot](#barebox) the system. + +#### DDI RAUC Upgrade Bundle + +**Canonical Name**: `${ARTIFACT}.pkg` + +A signed, verity formatted, [RAUC][] bundle containing the +[DDI](#discoverable-disk-image-ddi). On installation, the logical +volume of the inactive image is resized to fit the new DDI, which is +then written to it. + +When performing a [System Upgrade](#system-upgrade), this is the format +to use. + +#### DDI Disk Image + +**Canonical Names**: `${ARTIFACT}.disk`, `${ARTIFACT}.qcow2` + +A full system image, used to provision a new system, either by writing +`.disk` to its block device or by using `.qcow2` as the disk of a +virtual machine. The disk uses the GPT partitioning scheme: + +``` +.--------------. +| GPT Table | +:--------------: +| esp | +:--------------: +| esp-backup | +:--------------: +| barebox-state| +:--------------: +| | +| internal | +| | +'--------------' +``` + +##### `esp`, `esp-backup` - EFI System Partitions + +| **Parameter** | **Value** | +|---------------|------------------| +| Required | Yes | +| Size | 16 MiB | +| Format | VFAT filesystem | + +Holds the signed Barebox EFI binary, started by the UEFI firmware, as +well as marker files shared between Infix and its bootloader, e.g., a +pending factory reset. The partitions are mounted at `/boot/efi` and +`/boot/efi-backup`, respectively. + +##### `barebox-state` - Bootloader State + +| **Parameter** | **Value** | +|---------------|------------------------| +| Required | Yes | +| Size | 2 KiB | +| Format | Raw Barebox state data | + +Holds the bootchooser's priority and remaining boot attempts of each +boot target. Infix manages it through [RAUC][], see [Bootloader +Configuration](#configuration). + +##### `internal` - Internal Volume Group + +| **Parameter** | **Value** | +|---------------|----------------------| +| Required | Yes | +| Size | Rest of the disk | +| Format | LVM2 physical volume | + +An LVM physical volume, belonging to the volume group `internal`, +holding the following logical volumes: + +| **Volume** | **Contents** | +|-------------|--------------------------------------------------------| +| `primary` | A [DDI](#discoverable-disk-image-ddi) | +| `secondary` | A [DDI](#discoverable-disk-image-ddi) | +| `rw` | BTRFS filesystem holding all persistent data | + +Two copies of the system image exist so that an incomplete upgrade +does not brick the system, and to allow fast rollbacks when upgrading +to a new version. + +The `rw` volume holds one BTRFS subvolume each for `/cfg`, `/var`, +`/root`, and `/home`. If it cannot be mounted, Infix falls back to +temporary storage in RAM for all of them, and the system will not +retain any changes across reboots. + +To keep the distributed image small, it only contains the `primary` +volume. On the first boot, the system: + +1. Grows the `internal` partition, and its physical volume, to cover + the entire disk. +1. Creates the `secondary` volume as a copy of `primary`, provided + there is enough free space. +1. Creates the `rw` volume, using half of the remaining free space, + leaving the rest for future growth of the system images. + +A [factory reset](#system-boot) removes the `rw` volume, which is then +recreated, empty, during the following boot. + +### ITB Based Images + +#### FIT Framed Squash Image **Canonical Name**: `rootfs.itb` @@ -269,9 +446,9 @@ stored in the `/boot` directory of the filesystem. On disk, this image is then stored broken up into its two components; the _FIT header_ (`rootfs.itbh`) and the SquashFS image. The header is stored on the [Auxiliary Data](#aux-auxiliary-data) partition of -the [Disk Image](#disk-image), while the SquashFS image is stored in -one of the [Root Filesystem](#primarysecondary-root-filesystems) -partitions. +the [ITB Disk Image](#itb-disk-image), while the SquashFS image is +stored in one of the [Root +Filesystem](#primary-secondary-root-filesystems) partitions. When the system boots, U-Boot will concatenate the two parts to validate the SquashFS's contents. This path was chosen because: @@ -285,14 +462,14 @@ validate the SquashFS's contents. This path was chosen because: root filesystem. - It lets us use standard interfaces to boot Linux, like SYSLINUX. It - also plays well with traditional bootloaders, like GRUB. + also plays well with traditional bootloaders. In its full form, it can be used to netboot Infix, as it contains all the information needed by U-Boot in a single file. -### RAUC Upgrade Bundle +#### ITB RAUC Upgrade Bundle -**Canonical Name**: `infix-${ARCH}.pkg` +**Canonical Name**: `${ARTIFACT}.pkg` Itself a SquashFS image, this bundle (sometimes referred to package) contains the Infix [SquashFS Image](#squashfs-image) along with the @@ -303,7 +480,7 @@ system. When performing a [System Upgrade](#system-upgrade), this is the format to use. -### Disk Image +#### ITB Disk Image **Canonical Name**: `disk.img` @@ -335,7 +512,7 @@ scheme. Partitions marked with an asterisk are optional. '-----------' ``` -#### `boot` - Bootloader +##### `boot` - Bootloader | **Parameter** | **Value** | |---------------|-----------------------------------------| @@ -346,10 +523,7 @@ scheme. Partitions marked with an asterisk are optional. Optional partition containing the system's bootloader. May also reside in a separate storage device, e.g. a serial FLASH. -On x86_64, this partition holds the EFI system partition, containing -the GRUB bootloader. - -#### `aux` - Auxiliary Data +##### `aux` - Auxiliary Data | **Parameter** | **Value** | |---------------|-----------------| @@ -379,7 +553,7 @@ Note that the bootloader's primary environment is bundled in the binary - `uboot.env` is only used to import a few settings that is required to configure the boot order. -#### `primary`/`secondary` - Root Filesystems +##### `primary`, `secondary` - Root Filesystems | **Parameter** | **Value** | |---------------|-------------------| @@ -391,7 +565,7 @@ Holds the [SquashFS Image](#squashfs-image). Two copies exist so that an incomplete upgrade does not brick the system, and to allow fast rollbacks when upgrading to a new version. -#### `cfg` - Configuration Data +##### `cfg` - Configuration Data | **Parameter** | **Value** | |---------------|-----------------| @@ -402,7 +576,7 @@ rollbacks when upgrading to a new version. Non-volatile storage of the system configuration and user data. Concretely, user data is everything stored under `/root` and `/home`. -#### `var` - Variable Data +##### `var` - Variable Data | **Parameter** | **Value** | |---------------|-----------------| @@ -432,6 +606,8 @@ If `var` is not available, Infix will still persist `/var/lib` using between previously isolated VLANs. [2]: netboot.md +[DDI]: https://uapi-group.org/specifications/specs/discoverable_disk_image/ +[DPS]: https://uapi-group.org/specifications/specs/discoverable_partitions_specification/ [FIT]: https://u-boot.readthedocs.io/en/latest/usage/fit.html [RAUC]: https://rauc.io [initrd]: https://docs.kernel.org/admin-guide/initrd.html diff --git a/doc/netboot.md b/doc/netboot.md index bcd0174199..aa3451e0b6 100644 --- a/doc/netboot.md +++ b/doc/netboot.md @@ -1,15 +1,20 @@ Netboot HowTo ============= -This document describes how to set up network booting U-Boot devices on -a LAN, e.g., when working with an evaluation board or other embedded +This document describes how to set up network booting of devices on a +LAN, e.g., when working with an evaluation board or other embedded system. The most secure way to do this is with a local LAN between a PC and the device. -Instead of setting up everything in U-Boot to download the Linux Image, -device tree, and initramfs, we will let U-Boot download a script with -instructions to run. When you have multiple systems (boards) this -quickly becomes a lot easier to manage. +Both bootloaders used by Infix can netboot, see [Boot Procedure][boot]: + +- [Barebox](#barebox), on _x86_64_, downloads and boots a [Discoverable + Disk Image][ddi] (DDI) +- [U-Boot](#u-boot), on _aarch64_, downloads either a script with + instructions to run, or a `rootfs.itb` + +The [network interface](#network-interface-setup) and [DHCP/TFTP +server](#dhcptftp-server-setup) setup is the same for both. > [!NOTE] > Instructions in this HowTo assume a Debian based development system, @@ -85,7 +90,57 @@ dhcp-range=192.168.0.100,192.168.0.199,1h dhcp-boot=tag:eth2,bootfile-eth2 ``` -## Bootfile netboot.scr +## Barebox + +### Bootfile + +Barebox downloads whatever the DHCP server hands out as the boot file, +and boots it as a DDI from RAM. Point it straight at the DDI of the +build, `${ARTIFACT}.raw`, e.g.: + +``` +cd /srv/ftp +ln -sf ix/x86_64/infix-x86_64.raw /srv/ftp/bootfile-eth2 +``` + +The image is validated the same way as when booting from disk: with +UEFI _Secure Boot_ enabled, Barebox only boots a DDI whose signature +can be validated by a certificate in the firmware's signature database. +The running system uses the RAUC slot `net.0`, so it knows it came off +the network rather than from a slot. + +### Starting a Netboot + +The netboot target is disabled by default. To netboot once, interrupt +the Barebox autoboot from the console and run: + +``` +barebox@host:/# boot net +``` + +To make the system netboot on every boot, mark the netboot target +active from a running Infix: + +``` +root@example:~# rauc status mark-active net.0 +``` + +Should the netboot fail three times in a row, the bootchooser falls +back to the images on disk. See [Bootloader +Configuration][boot-cfg] for how to change back. + +### QEMU + +The same scenario can be tried in QEMU, using the scripts in the build's +`images/qemu` directory. In `make run-menuconfig`, select user mode +networking and *Enable netbooting* to have QEMU's built-in DHCP and +TFTP server hand out the DDI. Combined with an empty system disk, this +simulates an initial deployment. + + +## U-Boot + +### Bootfile netboot.scr The bootfile U-Boot retrieves from the TFTP server is a script that looks like this, `netboot.sh`: @@ -114,7 +169,7 @@ The output is `netboot.scr` which we symlink to above in the dnsmasq setup step. -## Bootfile rootfs.itb +### Bootfile rootfs.itb By default, the U-Boot downloads whatever the DHCP server hands out as the boot file, so point that straight at a `rootfs.itb`: @@ -140,7 +195,7 @@ and goes straight to DHCP. A factory fresh board therefore reaches a running system with nobody at the console. -## Installing to Onboard Storage +### Installing to Onboard Storage A netbooted device is a complete system, so it can install itself. Build the bootloader and the Infix image separately, then combine them: @@ -183,7 +238,7 @@ header with `sgdisk -e`, grows the last partition to fill the medium, and resizes the filesystem, in two stages with a reboot in between. -## Netboot with a Vendor U-Boot +### Netboot with a Vendor U-Boot The [Novarq Tactical 1000][tactical] ships with a vendor U-Boot that netboots but has neither `blkmap`, SquashFS support, nor `sysboot`, so it @@ -212,7 +267,7 @@ bootm 0x70000000#boot `brd.rd_size` is the SquashFS size in KiB. `fdt_high` and `initrd_high` need all 64 bits set, the shipped environment has a 32-bit `fdt_high`. -### Unattended +#### Unattended The `boot.scr` script runs the same steps. With the TFTP server on another host than the DHCP server, name it in `bootcmd`: @@ -242,7 +297,7 @@ dhcp-boot=boot.scr The image name is `${bootfile}` with `.scr` replaced by `.itb`. If the fetch fails the board resets after five seconds. -### Separate Artifacts +#### Separate Artifacts For trying a kernel or device tree without rebuilding the image: @@ -270,7 +325,7 @@ DRAM starts at `0x60000000`. [tactical]: https://github.com/kernelkit/infix/blob/main/board/aarch64/novarq-tactical-1000/README.md -## U-Boot Commands +### U-Boot Commands U-Boot is a maze of environment variables, some with values, some wrap commands, and most are undocumented. We will use a prefix for our @@ -283,4 +338,10 @@ later. ``` +[boot]: boot.md +[boot-cfg]: boot.md#configuration +[ddi]: boot.md#discoverable-disk-image-ddi +[ddi-disk]: boot.md#ddi-disk-image +[upgrade]: boot.md#system-upgrade + [^1]: To prevent NetworkManager from automatically managing the interfaces.