diff --git a/man/systemd-repart.xml b/man/systemd-repart.xml
index fb7b5496af..d05b74f57d 100644
--- a/man/systemd-repart.xml
+++ b/man/systemd-repart.xml
@@ -516,6 +516,47 @@
+
+ TYPE
+
+ Takes one of sysext, confext or
+ portable. Generates a Discoverable Disk Image (DDI) for a system extension
+ (sysext, see
+ systemd-sysext8
+ for details), configuration extension (confext) or portable service. The generated image will consist
+ of a signed Verity erofs file system as root partition. In this mode of operation
+ the partition definitions in /usr/lib/repart.d/*.conf and related directories
+ are not read, and is not supported, as appropriate definitions for
+ the selected DDI class will be chosen automatically.
+
+ Must be used in conjunction with to specify the file hierarchy
+ to populate the DDI with. The specified directory should contain an etc/
+ subdirectory if confext is selected. If sysext is selected it
+ should contain either a usr/ or opt/ directory, or both. If
+ portable is used a full OS file hierarchy can be provided.
+
+ This option implies , and
+ (the latter two can be overriden).
+
+ The private key and certificate for signing the DDI must be specified via the
+ and switches.
+
+
+
+
+
+
+
+
+
+ Shortcuts for ,
+ , ,
+ respectively.
+
+
+
+
@@ -530,13 +571,41 @@
On success, 0 is returned, a non-zero failure code otherwise.
+
+ Example
+
+
+ Generate a configuration extension image
+
+ The following creates a configuration extension DDI (confext) for an
+ /etc/motd update.
+
+ mkdir tree tree/etc tree/etc/extension-release.d
+echo "Hello World" > tree/etc/motd
+cat > tree/etc/extension-release.d/extension-release.my-motd <<EOF
+ID=fedora
+VERSION_ID=38
+IMAGE_ID=my-motd
+IMAGE_VERSION=7
+EOF
+systemd-repart -C --private-key=privkey.pem --certificate=cert.crt -s tree/ /var/lib/confexts/my-motd.confext.raw
+systemd-confext refresh
+
+ The DDI generated that way may be applied to the system with
+ systemd-confext1.
+
+
+
+
See Alsosystemd1,
repart.d5,
machine-id5,
- systemd-cryptenroll1
+ systemd-cryptenroll1,
+ portablectl1,
+ systemd-sysext8
diff --git a/meson.build b/meson.build
index 1e59d353ee..88bc1367e0 100644
--- a/meson.build
+++ b/meson.build
@@ -171,6 +171,7 @@ systemdstatedir = localstatedir / 'lib/systemd'
catalogstatedir = systemdstatedir / 'catalog'
randomseeddir = localstatedir / 'lib/systemd'
profiledir = libexecdir / 'portable' / 'profile'
+repartdefinitionsdir = libexecdir / 'repart/definitions'
ntpservicelistdir = prefixdir / 'lib/systemd/ntp-units.d'
credstoredir = prefixdir / 'lib/credstore'
diff --git a/src/partition/definitions/confext.repart.d/10-root.conf b/src/partition/definitions/confext.repart.d/10-root.conf
new file mode 100644
index 0000000000..e41dc0578b
--- /dev/null
+++ b/src/partition/definitions/confext.repart.d/10-root.conf
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Partition]
+Type=root
+Format=erofs
+CopyFiles=/etc/
+Verity=data
+VerityMatchKey=root
diff --git a/src/partition/definitions/confext.repart.d/20-root-verity.conf b/src/partition/definitions/confext.repart.d/20-root-verity.conf
new file mode 100644
index 0000000000..437d88e068
--- /dev/null
+++ b/src/partition/definitions/confext.repart.d/20-root-verity.conf
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Partition]
+Type=root-verity
+Verity=hash
+VerityMatchKey=root
diff --git a/src/partition/definitions/confext.repart.d/30-root-verity-sig.conf b/src/partition/definitions/confext.repart.d/30-root-verity-sig.conf
new file mode 100644
index 0000000000..df160154a7
--- /dev/null
+++ b/src/partition/definitions/confext.repart.d/30-root-verity-sig.conf
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Partition]
+Type=root-verity-sig
+Verity=signature
+VerityMatchKey=root
diff --git a/src/partition/definitions/portable.repart.d/10-root.conf b/src/partition/definitions/portable.repart.d/10-root.conf
new file mode 100644
index 0000000000..78758002f5
--- /dev/null
+++ b/src/partition/definitions/portable.repart.d/10-root.conf
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Partition]
+Type=root
+Format=erofs
+CopyFiles=/
+Verity=data
+VerityMatchKey=root
diff --git a/src/partition/definitions/portable.repart.d/20-root-verity.conf b/src/partition/definitions/portable.repart.d/20-root-verity.conf
new file mode 100644
index 0000000000..437d88e068
--- /dev/null
+++ b/src/partition/definitions/portable.repart.d/20-root-verity.conf
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Partition]
+Type=root-verity
+Verity=hash
+VerityMatchKey=root
diff --git a/src/partition/definitions/portable.repart.d/30-root-verity-sig.conf b/src/partition/definitions/portable.repart.d/30-root-verity-sig.conf
new file mode 100644
index 0000000000..df160154a7
--- /dev/null
+++ b/src/partition/definitions/portable.repart.d/30-root-verity-sig.conf
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Partition]
+Type=root-verity-sig
+Verity=signature
+VerityMatchKey=root
diff --git a/src/partition/definitions/sysext.repart.d/10-root.conf b/src/partition/definitions/sysext.repart.d/10-root.conf
new file mode 100644
index 0000000000..41a7ca56d7
--- /dev/null
+++ b/src/partition/definitions/sysext.repart.d/10-root.conf
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Partition]
+Type=root
+Format=erofs
+CopyFiles=/usr/ /opt/
+Verity=data
+VerityMatchKey=root
diff --git a/src/partition/definitions/sysext.repart.d/20-root-verity.conf b/src/partition/definitions/sysext.repart.d/20-root-verity.conf
new file mode 100644
index 0000000000..437d88e068
--- /dev/null
+++ b/src/partition/definitions/sysext.repart.d/20-root-verity.conf
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Partition]
+Type=root-verity
+Verity=hash
+VerityMatchKey=root
diff --git a/src/partition/definitions/sysext.repart.d/30-root-verity-sig.conf b/src/partition/definitions/sysext.repart.d/30-root-verity-sig.conf
new file mode 100644
index 0000000000..df160154a7
--- /dev/null
+++ b/src/partition/definitions/sysext.repart.d/30-root-verity-sig.conf
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Partition]
+Type=root-verity-sig
+Verity=signature
+VerityMatchKey=root
diff --git a/src/partition/meson.build b/src/partition/meson.build
index 19f49f606e..78cde2ff52 100644
--- a/src/partition/meson.build
+++ b/src/partition/meson.build
@@ -48,3 +48,15 @@ executables += [
'install' : have_standalone_binaries,
},
]
+
+if conf.get('ENABLE_REPART') == 1
+ install_data('definitions/confext.repart.d/10-root.conf', install_dir : repartdefinitionsdir / 'confext.repart.d')
+ install_data('definitions/confext.repart.d/20-root-verity.conf', install_dir : repartdefinitionsdir / 'confext.repart.d')
+ install_data('definitions/confext.repart.d/30-root-verity-sig.conf', install_dir : repartdefinitionsdir / 'confext.repart.d')
+ install_data('definitions/portable.repart.d/10-root.conf', install_dir : repartdefinitionsdir / 'portable.repart.d')
+ install_data('definitions/portable.repart.d/20-root-verity.conf', install_dir : repartdefinitionsdir / 'portable.repart.d')
+ install_data('definitions/portable.repart.d/30-root-verity-sig.conf', install_dir : repartdefinitionsdir / 'portable.repart.d')
+ install_data('definitions/sysext.repart.d/10-root.conf', install_dir : repartdefinitionsdir / 'sysext.repart.d')
+ install_data('definitions/sysext.repart.d/20-root-verity.conf', install_dir : repartdefinitionsdir / 'sysext.repart.d')
+ install_data('definitions/sysext.repart.d/30-root-verity-sig.conf', install_dir : repartdefinitionsdir / 'sysext.repart.d')
+endif
diff --git a/src/partition/repart.c b/src/partition/repart.c
index 770f7c48ff..855e908d75 100644
--- a/src/partition/repart.c
+++ b/src/partition/repart.c
@@ -163,6 +163,7 @@ static Architecture arg_architecture = _ARCHITECTURE_INVALID;
static int arg_offline = -1;
static char **arg_copy_from = NULL;
static char *arg_copy_source = NULL;
+static char *arg_make_ddi = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
@@ -177,6 +178,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_filter_partitions, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
STATIC_DESTRUCTOR_REGISTER(arg_copy_from, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_copy_source, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_make_ddi, freep);
typedef struct FreeArea FreeArea;
@@ -6376,6 +6378,9 @@ static int help(void) {
" --offline=BOOL Whether to build the image offline\n"
" -s --copy-source=PATH Specify the primary source tree to copy files from\n"
" --copy-from=IMAGE Copy partitions from the given image(s)\n"
+ " -S --make-ddi=sysext Make a system extension DDI\n"
+ " -C --make-ddi=confext Make a configuration extension DDI\n"
+ " -P --make-ddi=portable Make a portable service DDI\n"
"\nSee the %s for details.\n",
program_invocation_short_name,
ansi_highlight(),
@@ -6420,7 +6425,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_ARCHITECTURE,
ARG_OFFLINE,
ARG_COPY_FROM,
- ARG_COPY_SOURCE,
+ ARG_MAKE_DDI,
};
static const struct option options[] = {
@@ -6457,6 +6462,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "offline", required_argument, NULL, ARG_OFFLINE },
{ "copy-from", required_argument, NULL, ARG_COPY_FROM },
{ "copy-source", required_argument, NULL, 's' },
+ { "make-ddi", required_argument, NULL, ARG_MAKE_DDI },
{}
};
@@ -6465,7 +6471,7 @@ static int parse_argv(int argc, char *argv[]) {
assert(argc >= 0);
assert(argv);
- while ((c = getopt_long(argc, argv, "hs:", options, NULL)) >= 0)
+ while ((c = getopt_long(argc, argv, "hs:SCP", options, NULL)) >= 0)
switch (c) {
@@ -6796,6 +6802,33 @@ static int parse_argv(int argc, char *argv[]) {
return r;
break;
+ case ARG_MAKE_DDI:
+ if (!filename_is_valid(optarg))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid DDI type: %s", optarg);
+
+ r = free_and_strdup_warn(&arg_make_ddi, optarg);
+ if (r < 0)
+ return r;
+ break;
+
+ case 'S':
+ r = free_and_strdup_warn(&arg_make_ddi, "sysext");
+ if (r < 0)
+ return r;
+ break;
+
+ case 'C':
+ r = free_and_strdup_warn(&arg_make_ddi, "confext");
+ if (r < 0)
+ return r;
+ break;
+
+ case 'P':
+ r = free_and_strdup_warn(&arg_make_ddi, "portable");
+ if (r < 0)
+ return r;
+ break;
+
case '?':
return -EINVAL;
@@ -6807,6 +6840,38 @@ static int parse_argv(int argc, char *argv[]) {
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Expected at most one argument, the path to the block device.");
+ if (arg_make_ddi) {
+ if (arg_definitions)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Combination of --make-ddi= and --definitions= is not supported.");
+ if (!IN_SET(arg_empty, EMPTY_UNSET, EMPTY_CREATE))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Combination of --make-ddi= and --empty=%s is not supported.",
+ arg_empty == EMPTY_REFUSE ? "refuse" :
+ arg_empty == EMPTY_ALLOW ? "allow" :
+ arg_empty == EMPTY_REQUIRE ? "require" : "force");
+
+ /* Imply automatic sizing in DDI mode */
+ if (arg_size == UINT64_MAX)
+ arg_size_auto = true;
+
+ if (!arg_copy_source)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No --copy-source= specified, refusing.");
+
+ r = dir_is_empty(arg_copy_source, /* ignore_hidden_or_backup= */ false);
+ if (r < 0)
+ return log_error_errno(r, "Failed to determine if '%s' is empty: %m", arg_copy_source);
+ if (r > 0)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Source directory '%s' is empty, refusing to create empty image.", arg_copy_source);
+
+ if (sd_id128_is_null(arg_seed) && !arg_randomize) {
+ /* We don't want that /etc/machine-id leaks into any image built this way, hence
+ * let's randomize the seed if not specified explicitly */
+ log_notice("No seed value specified, randomizing generated UUIDs, resulting image will not be reproducible.");
+ arg_randomize = true;
+ }
+
+ arg_empty = EMPTY_CREATE;
+ }
+
if (arg_empty == EMPTY_UNSET) /* default to refuse mode, if not otherwise specified */
arg_empty = EMPTY_REFUSE;
@@ -7371,7 +7436,22 @@ static int run(int argc, char *argv[]) {
if (r < 0)
return r;
- strv_uniq(arg_definitions);
+ if (arg_make_ddi) {
+ _cleanup_free_ char *d = NULL, *dp = NULL;
+ assert(!arg_definitions);
+
+ d = strjoin(arg_make_ddi, ".repart.d/");
+ if (!d)
+ return log_oom();
+
+ r = search_and_access(d, F_OK, arg_root, CONF_PATHS_USR_STRV("systemd/repart/definitions"), &dp);
+ if (r < 0)
+ return log_error_errno(errno, "DDI type '%s' is not defined: %m", arg_make_ddi);
+
+ if (strv_consume(&arg_definitions, TAKE_PTR(dp)) < 0)
+ return log_oom();
+ } else
+ strv_uniq(arg_definitions);
r = context_read_definitions(context);
if (r < 0)