diff --git a/docs/FACTORY_RESET.md b/docs/FACTORY_RESET.md
index bb0e3063b11..8004a80b787 100644
--- a/docs/FACTORY_RESET.md
+++ b/docs/FACTORY_RESET.md
@@ -56,6 +56,14 @@ Specifically, the following concepts are available:
and adds `factory-reset-now.target` to the boot transaction, already in the
initial RAM disk (initrd).
+* The
+ [`systemd-factory-reset-esp.service`](https://www.freedesktop.org/software/systemd/man/latest/systemd-factory-reset-esp.service.html)
+ unit is invoked via `factory-reset-now.target`, and deletes non-vendor system
+ extension images, UKI addons, and credentials from the EFI System and Extended
+ Bootloader Partitions. See the
+ [`systemd-stub(7)`](https://www.freedesktop.org/software/systemd/man/latest/systemd-stub.html)
+ man page.
+
* The
[`systemd-factory-reset-complete.service`](https://www.freedesktop.org/software/systemd/man/latest/systemd-factory-reset-complete.service.html)
unit is invoked after `factory-reset-now.target` and marks the factory reset
@@ -118,8 +126,9 @@ order to execute the reset operation.
## Support for Resetting other Resources than Partitions + TPM
By default a factory reset implemented with systemd's tools can reset/erase
-partitions (via `systemd-repart`, see above) and reset the TPM (via
-`systemd-tpm2-clear.service`, see above).
+partitions (via `systemd-repart`, see above), reset the TPM (via
+`systemd-tpm2-clear.service`, see above), and delete non-vendor resources from
+the ESP (via `systemd-factory-reset-esp.service`, see above).
In some cases other resources shall be reset/erased too. To support that,
define your own service and plug it into `factory-reset-now.target`, ensuring
diff --git a/man/rules/meson.build b/man/rules/meson.build
index 2b06442d62d..21970eedc2c 100644
--- a/man/rules/meson.build
+++ b/man/rules/meson.build
@@ -942,6 +942,7 @@ manpages = [
['30-systemd-environment-d-generator'],
'ENABLE_ENVIRONMENT_D'],
['systemd-escape', '1', [], ''],
+ ['systemd-factory-reset-esp.service', '8', [], ''],
['systemd-factory-reset-generator', '8', [], ''],
['systemd-factory-reset',
'8',
diff --git a/man/systemd-factory-reset-esp.xml b/man/systemd-factory-reset-esp.xml
new file mode 100644
index 00000000000..942e6881b60
--- /dev/null
+++ b/man/systemd-factory-reset-esp.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+ systemd-factory-reset-esp.service
+ systemd
+
+
+
+ systemd-factory-reset-esp.service
+ 8
+
+
+
+ systemd-factory-reset-esp.service
+ Delete non-vendor content from the ESP and XBOOTLDR
+
+
+
+ /usr/lib/systemd/systemd-factory-reset-esp
+ systemd-factory-reset-esp.service
+
+
+
+ Description
+
+ systemd-factory-reset-esp.service is a part of systemd's factory reset logic,
+ which deletes non-vendor system extension images, UKI addons, and credentials from the EFI System and
+ Extended Bootloader Partitions. The vendor and non-vendor versions of these resources are stored in
+ different directories, as described in systemd-stub
+ 7.
+
+ See Factory Reset for an overview of the
+ factory reset logic.
+
+
+
+ See Also
+
+ systemd1
+ systemd-factory-reset8
+ Factory Reset
+
+
+
diff --git a/man/systemd-stub.xml b/man/systemd-stub.xml
index 273513c14bd..ec75b9677f2 100644
--- a/man/systemd-stub.xml
+++ b/man/systemd-stub.xml
@@ -207,10 +207,17 @@
details on system extension images. The generated cpio archive containing these
system extension images is measured into TPM PCR 13 (if a TPM is present).
-
+ Similarly, files
+ foo.efi.extra.vendor.d/*.sysext.raw are packed up in a
+ cpio archive and placed in the /.extra/sysext/ directory in the
+ initrd file heirarchy. This alternate directory exists for OS vendors to ship system extension
+ images that shall persist across an OS factory reset. This archive is similarly measured into TPM
+ PCR 13 (if a TPM is present).
+
Similarly, files
foo.efi.extra.d/*.confext.raw are packed up in a
cpio archive and placed in the /.extra/confext/ directory in
@@ -221,13 +228,23 @@
these configuration extension images is measured into TPM PCR 12 (if a TPM is present).
Similarly, files
- foo.efi.extra.d/*.addon.efi are loaded and verified as
- PE binaries and specific sections are loaded from them. Addons are used to pass additional kernel
- command line parameters (.cmdline section), or DeviceTree blobs
+ foo.efi.extra.vendor.d/*.confext.raw are packed up in a
+ cpio archive and placed in the /.extra/confext/ directory in
+ the initrd file heirarchy. This archive is similarly measured into TPM PCR 12 (if a TPM is present).
+
+
+ Similarly, files
+ foo.efi.extra.d/*.addon.efi,
+ foo.efi.extra.vendor.d/*.addon.efi,
+ /loader/addons/*.addon.efi, and
+ /loader/vendor-addons/*.addon.efi are loaded and
+ verified as PE binaries and specific sections are loaded from them. Addons are used to pass additional
+ kernel command line parameters (.cmdline section), or Devicetree blobs
(.dtb section), additional initrds (.initrd section),
- and microcode updates (.ucode section). Addons allow those resources to be passed
- regardless of the kernel version being booted, for example allowing platform vendors to ship
- platform-specific configuration.
+ and microcode updates (.ucode section). The addons found under
+ /loader/addons/ or /loader/vendor-addons/ allow those
+ resources to be passed regardless of the kernel version being booted, for example allowing platform
+ vendors to ship platform-specific configuration.
In case Secure Boot is enabled, these files will be validated using keys in UEFI DB, Shim's DB or
Shim's MOK, and only loaded if the check passes. Additionally, if both the addon and the UKI contain a
@@ -239,8 +256,8 @@
Shim documentation.
Addon files are sorted, loaded, and measured into TPM PCR 12 (if a TPM is present) and appended
- to the kernel command line. UKI command line options are listed first, then options from addons in
- /loader/addons/*.addon.efi, and finally UKI-specific addons. Device tree blobs are
+ to the kernel command line. UKI command line options are listed first, then options from global addons in
+ /loader/addons/ and others, and finally UKI-specific addons. Device tree blobs are
loaded and measured following the same algorithm. Microcode addons are passed to the kernel in inverse
order (UKI specific addons, global addons, UKI embedded section). This is because the microcode update
driver stops on the first matching filename. Addons are always loaded in the same order based on
@@ -252,17 +269,11 @@
an attestation service so that improper use of your signed addons can be detected and dealt with using
one of the aforementioned revocation mechanisms.
- Files /loader/credentials/*.cred are packed up in a
+ Similarly, files /loader/credentials/*.cred are packed up in a
cpio archive and placed in the /.extra/global_credentials/
directory of the initrd file hierarchy. This is supposed to be used to pass additional credentials to
the initrd, regardless of the kernel version being booted. The generated cpio
archive is measured into TPM PCR 12 (if a TPM is present).
-
- Additionally, files /loader/addons/*.addon.efi are loaded and
- verified as PE binaries, and .cmdline, .dtb,
- .initrd, and .ucode sections are parsed from them.
- This is supposed to be used to pass additional command line parameters, DeviceTree blobs, initrds,
- and microcode updates to the kernel, regardless of the kernel version being booted.
These mechanisms may be used to parameterize and extend trusted (i.e. signed), immutable initrd
diff --git a/src/boot/cpio.c b/src/boot/cpio.c
index fc5e303d7e6..7bbdc0fda6b 100644
--- a/src/boot/cpio.c
+++ b/src/boot/cpio.c
@@ -316,7 +316,6 @@ EFI_STATUS pack_cpio(
_cleanup_file_close_ EFI_FILE *root = NULL, *extra_dir = NULL;
size_t dirent_size = 0, buffer_size = 0, n_items = 0, n_allocated = 0;
- _cleanup_free_ char16_t *rel_dropin_dir = NULL;
_cleanup_free_ EFI_FILE_INFO *dirent = NULL;
_cleanup_strv_free_ char16_t **items = NULL;
_cleanup_free_ void *buffer = NULL;
@@ -330,6 +329,9 @@ EFI_STATUS pack_cpio(
if (!loaded_image->DeviceHandle)
goto nothing;
+ if (!dropin_dir)
+ goto nothing;
+
err = open_volume(loaded_image->DeviceHandle, &root);
if (err == EFI_UNSUPPORTED)
/* Error will be unsupported if the bootloader doesn't implement the file system protocol on
@@ -338,12 +340,6 @@ EFI_STATUS pack_cpio(
if (err != EFI_SUCCESS)
return log_error_status(err, "Unable to open root directory: %m");
- if (!dropin_dir) {
- dropin_dir = rel_dropin_dir = get_extra_dir(loaded_image->FilePath);
- if (!dropin_dir)
- goto nothing;
- }
-
err = open_directory(root, dropin_dir, &extra_dir);
if (err == EFI_NOT_FOUND)
/* No extra subdir, that's totally OK */
diff --git a/src/boot/stub.c b/src/boot/stub.c
index 4793391b06d..76ca4847847 100644
--- a/src/boot/stub.c
+++ b/src/boot/stub.c
@@ -37,7 +37,9 @@ enum {
INITRD_CREDENTIAL = _INITRD_DYNAMIC_FIRST,
INITRD_GLOBAL_CREDENTIAL,
INITRD_SYSEXT,
+ INITRD_VENDOR_SYSEXT,
INITRD_CONFEXT,
+ INITRD_VENDOR_CONFEXT,
INITRD_PCRSIG,
INITRD_PCRPKEY,
INITRD_OSREL,
@@ -821,6 +823,7 @@ static void generate_sidecar_initrds(
int *sysext_measured,
int *confext_measured) {
+ _cleanup_free_ char16_t *dropin_dir = NULL, *vendor_dropin_dir = NULL;
bool m;
assert(loaded_image);
@@ -829,8 +832,13 @@ static void generate_sidecar_initrds(
assert(sysext_measured);
assert(confext_measured);
+
+ dropin_dir = get_extra_dir(".extra.d", loaded_image->FilePath);
+ vendor_dropin_dir = get_extra_dir(".extra.vendor.d", loaded_image->FilePath);
+ /* Note: Either of these may be NULL, but that's handled by pack_cpio */
+
if (pack_cpio(loaded_image,
- /* dropin_dir= */ NULL,
+ dropin_dir,
u".cred",
/* exclude_suffix= */ NULL,
".extra/credentials",
@@ -856,7 +864,7 @@ static void generate_sidecar_initrds(
combine_measured_flag(parameters_measured, m);
if (pack_cpio(loaded_image,
- /* dropin_dir= */ NULL,
+ dropin_dir,
u".raw", /* ideally we'd pick up only *.sysext.raw here, but for compat we pick up *.raw instead … */
u".confext.raw", /* … but then exclude *.confext.raw again */
".extra/sysext",
@@ -869,7 +877,20 @@ static void generate_sidecar_initrds(
combine_measured_flag(sysext_measured, m);
if (pack_cpio(loaded_image,
- /* dropin_dir= */ NULL,
+ vendor_dropin_dir,
+ u".sysext.raw", /* No compatibility to worry about in the new .vendor.d dirs */
+ /* exclude_suffix= */ NULL,
+ ".extra/sysext",
+ /* dir_mode= */ 0555,
+ /* access_mode= */ 0444,
+ /* tpm_pcr= */ TPM2_PCR_SYSEXTS,
+ u"Vendor system extension initrd",
+ initrds + INITRD_VENDOR_SYSEXT,
+ &m) == EFI_SUCCESS)
+ combine_measured_flag(sysext_measured, m);
+
+ if (pack_cpio(loaded_image,
+ dropin_dir,
u".confext.raw",
/* exclude_suffix= */ NULL,
".extra/confext",
@@ -880,6 +901,19 @@ static void generate_sidecar_initrds(
initrds + INITRD_CONFEXT,
&m) == EFI_SUCCESS)
combine_measured_flag(confext_measured, m);
+
+ if (pack_cpio(loaded_image,
+ vendor_dropin_dir,
+ u".confext.raw",
+ /* exclude_suffix= */ NULL,
+ ".extra/confext",
+ /* dir_mode= */ 0555,
+ /* access_mode= */ 0444,
+ /* tpm_pcr= */ TPM2_PCR_KERNEL_CONFIG,
+ u"Vendor configuration extension initrd",
+ initrds + INITRD_VENDOR_CONFEXT,
+ &m) == EFI_SUCCESS)
+ combine_measured_flag(confext_measured, m);
}
static void generate_embedded_initrds(
@@ -1042,8 +1076,23 @@ static void load_all_addons(
if (err != EFI_SUCCESS)
log_error_status(err, "Error loading global addons, ignoring: %m");
+ err = load_addons(
+ image,
+ loaded_image,
+ u"\\loader\\vendor-addons",
+ uname,
+ cmdline_addons,
+ dt_addons,
+ n_dt_addons,
+ initrd_addons,
+ n_initrd_addons,
+ ucode_addons,
+ n_ucode_addons);
+ if (err != EFI_SUCCESS)
+ log_error_status(err, "Error loading global vendor addons, ignoring: %m");
+
/* Some bootloaders always pass NULL in FilePath, so we need to check for it here. */
- _cleanup_free_ char16_t *dropin_dir = get_extra_dir(loaded_image->FilePath);
+ _cleanup_free_ char16_t *dropin_dir = get_extra_dir(".extra.d", loaded_image->FilePath);
if (!dropin_dir)
return;
@@ -1061,6 +1110,26 @@ static void load_all_addons(
n_ucode_addons);
if (err != EFI_SUCCESS)
log_error_status(err, "Error loading UKI-specific addons, ignoring: %m");
+
+ _cleanup_free_ char16_t *vendor_dropin_dir =
+ get_extra_dir(".extra.vendor.d", loaded_image->FilePath);
+ if (!vendor_dropin_dir)
+ return;
+
+ err = load_addons(
+ image,
+ loaded_image,
+ vendor_dropin_dir,
+ uname,
+ cmdline_addons,
+ dt_addons,
+ n_dt_addons,
+ initrd_addons,
+ n_initrd_addons,
+ ucode_addons,
+ n_ucode_addons);
+ if (err != EFI_SUCCESS)
+ log_error_status(err, "Error loading UKI-specific vendor addons, ignoring: %m");
}
static void display_splash(
diff --git a/src/boot/util.c b/src/boot/util.c
index 5dd111e7c5a..7f130b5ad3a 100644
--- a/src/boot/util.c
+++ b/src/boot/util.c
@@ -462,7 +462,7 @@ static void remove_boot_count(char16_t *path) {
strcpy16(prefix_end, tail);
}
-char16_t *get_extra_dir(const EFI_DEVICE_PATH *file_path) {
+char16_t *get_extra_dir(const char *suffix, const EFI_DEVICE_PATH *file_path) {
if (!file_path)
return NULL;
@@ -483,7 +483,7 @@ char16_t *get_extra_dir(const EFI_DEVICE_PATH *file_path) {
convert_efi_path(file_path_str);
remove_boot_count(file_path_str);
- return xasprintf("%ls.extra.d", file_path_str);
+ return xasprintf("%ls%s", file_path_str, suffix);
}
void *xmalloc(size_t size) {
diff --git a/src/boot/util.h b/src/boot/util.h
index e1d65770d6c..e0905628dbd 100644
--- a/src/boot/util.h
+++ b/src/boot/util.h
@@ -239,7 +239,7 @@ static inline bool efi_guid_equal(const EFI_GUID *a, const EFI_GUID *b) {
void *find_configuration_table(const EFI_GUID *guid);
-char16_t *get_extra_dir(const EFI_DEVICE_PATH *file_path);
+char16_t *get_extra_dir(const char *suffix, const EFI_DEVICE_PATH *file_path);
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
# define be32toh(x) __builtin_bswap32(x)
diff --git a/src/factory-reset/factory-reset-esp.c b/src/factory-reset/factory-reset-esp.c
new file mode 100644
index 00000000000..a50f0b6ba16
--- /dev/null
+++ b/src/factory-reset/factory-reset-esp.c
@@ -0,0 +1,95 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include
+
+#include "ansi-color.h"
+#include "bootspec.h"
+#include "find-esp.h"
+#include "main-func.h"
+
+static int help(void) {
+ _cleanup_free_ char *link = NULL;
+ int r;
+
+ r = terminal_urlify_man("systemd-factory-reset-esp.service", "8", &link);
+ if (r < 0)
+ return log_oom();
+
+ printf("%1$s [OPTIONS...]\n"
+ "\n%5$sDelete non-vendor contents from ESP and XBOOTLDR.%6$s\n"
+ "\n%3$sOptions:%4$s\n"
+ " -h --help Show this help\n"
+ " --version Print version\n"
+ "\nSee the %2$s for details.\n",
+ program_invocation_short_name,
+ link,
+ ansi_underline(),
+ ansi_normal(),
+ ansi_highlight(),
+ ansi_normal());
+
+ return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+ enum {
+ ARG_VERSION = 0x100,
+ };
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ {}
+ };
+
+ int r, c;
+
+ assert(argc >= 0);
+ assert(argv);
+
+ while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
+ switch (c) {
+
+ case 'h':
+ return help();
+
+ case ARG_VERSION:
+ return version();
+
+ case '?':
+ return -EINVAL;
+
+ default:
+ assert_not_reached();
+ }
+
+ return 1;
+}
+
+static int run(int argc, char *argv[]) {
+ int r;
+
+ log_setup();
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ return r;
+
+ FactoryResetMode f = factory_reset_mode();
+ if (f < 0)
+ return log_error_errno(f, "Failed to determine factory reset mode: %m");
+ if (f != FACTORY_RESET_ON)
+ return log_error("We are not currently in factory reset mode. Refusing operation.");
+
+
+ _cleanup_(boot_config_free) BootConfig bc = BOOT_CONFIG_NULL;
+ r = boot_config_load_auto(&bc, NULL, NULL);
+ if (r < 0)
+ log_error_errno(r, "Failed to load boot config: %m");
+
+ // TODO: Walk the ESP and XBOOTLDR, and delete the non-vendor stuff!
+
+ return 0;
+}
+
+DEFINE_MAIN_FUNCTION(run);
diff --git a/src/factory-reset/meson.build b/src/factory-reset/meson.build
index 1afac3ad64e..9ac9c833c61 100644
--- a/src/factory-reset/meson.build
+++ b/src/factory-reset/meson.build
@@ -5,6 +5,10 @@ executables += [
'name' : 'systemd-factory-reset',
'sources' : files('factory-reset-tool.c'),
},
+ libexec_template + {
+ 'name' : 'systemd-factory-reset-esp',
+ 'sources' : files('factory-reset-esp.c'),
+ },
generator_template + {
'name' : 'systemd-factory-reset-generator',
'sources' : files('factory-reset-generator.c'),
diff --git a/src/shared/bootspec.c b/src/shared/bootspec.c
index 12d2a9d1472..35b33eca14e 100644
--- a/src/shared/bootspec.c
+++ b/src/shared/bootspec.c
@@ -1090,14 +1090,13 @@ static int boot_entries_find_unified_addons(
int d_fd,
const char *addon_dir,
const char *root,
- BootEntryAddons *ret_addons) {
+ BootEntryAddons *addons) {
_cleanup_closedir_ DIR *d = NULL;
_cleanup_free_ char *full = NULL;
- _cleanup_(boot_entry_addons_done) BootEntryAddons addons = {};
int r;
- assert(ret_addons);
+ assert(addons);
assert(config);
r = chase_and_opendirat(d_fd, addon_dir, CHASE_AT_RESOLVE_IN_ROOT, &full, &d);
@@ -1139,15 +1138,13 @@ static int boot_entries_find_unified_addons(
if (!location)
return log_oom();
- r = insert_boot_entry_addon(&addons, location, cmdline);
+ r = insert_boot_entry_addon(addons, location, cmdline);
if (r < 0)
return r;
-
TAKE_PTR(location);
TAKE_PTR(cmdline);
}
- *ret_addons = TAKE_STRUCT(addons);
return 0;
}
@@ -1158,17 +1155,28 @@ static int boot_entries_find_unified_global_addons(
BootEntryAddons *ret_addons) {
int r;
+ _cleanup_free_ char *p = NULL;
_cleanup_closedir_ DIR *d = NULL;
+ _cleanup_(boot_entry_addons_done) BootEntryAddons addons = {};
assert(ret_addons);
- r = chase_and_opendir(root, NULL, CHASE_PROHIBIT_SYMLINKS, NULL, &d);
+ r = chase_and_opendir(d_name, root, CHASE_PROHIBIT_SYMLINKS, &p, &d);
if (r == -ENOENT)
return 0;
if (r < 0)
return log_error_errno(r, "Failed to open '%s/%s': %m", root, d_name);
- return boot_entries_find_unified_addons(config, dirfd(d), d_name, root, ret_addons);
+ r = boot_entries_find_unified_addons(config, dirfd(d), "addons", p, &addons);
+ if (r < 0)
+ return r;
+
+ r = boot_entries_find_unified_addons(config, dirfd(d), "vendor-addons", p, &addons);
+ if (r < 0)
+ return r;
+
+ *ret_addons = TAKE_STRUCT(addons);
+ return 0;
}
static int boot_entries_find_unified_local_addons(
@@ -1178,7 +1186,8 @@ static int boot_entries_find_unified_local_addons(
const char *root,
BootEntry *ret) {
- _cleanup_free_ char *addon_dir = NULL;
+ _cleanup_free_ char *addon_dir = NULL, *vendor_addon_dir = NULL;
+ int r;
assert(ret);
@@ -1186,7 +1195,15 @@ static int boot_entries_find_unified_local_addons(
if (!addon_dir)
return log_oom();
- return boot_entries_find_unified_addons(config, d_fd, addon_dir, root, &ret->local_addons);
+ vendor_addon_dir = strjoin(d_name, ".extra.vendor.d");
+ if (!vendor_addon_dir)
+ return log_oom();
+
+ r = boot_entries_find_unified_addons(config, d_fd, addon_dir, root, &ret->local_addons);
+ if (r < 0)
+ return r;
+
+ return boot_entries_find_unified_addons(config, d_fd, vendor_addon_dir, root, &ret->local_addons);
}
static int boot_entries_find_unified(
@@ -1485,7 +1502,7 @@ int boot_config_load(
if (r < 0)
return r;
- r = boot_entries_find_unified_global_addons(config, esp_path, "/loader/addons/",
+ r = boot_entries_find_unified_global_addons(config, esp_path, "/loader",
&config->global_addons[BOOT_ENTRY_ESP]);
if (r < 0)
return r;
@@ -1500,7 +1517,7 @@ int boot_config_load(
if (r < 0)
return r;
- r = boot_entries_find_unified_global_addons(config, xbootldr_path, "/loader/addons/",
+ r = boot_entries_find_unified_global_addons(config, xbootldr_path, "/loader",
&config->global_addons[BOOT_ENTRY_XBOOTLDR]);
if (r < 0)
return r;
diff --git a/units/meson.build b/units/meson.build
index b29bed068f6..5665bcc55c9 100644
--- a/units/meson.build
+++ b/units/meson.build
@@ -329,6 +329,7 @@ units = [
'symlinks' : ['sockets.target.wants/'],
},
{ 'file' : 'systemd-factory-reset-complete.service.in' },
+ { 'file' : 'systemd-factory-reset-esp.service' },
{ 'file' : 'systemd-factory-reset-reboot.service' },
{
'file' : 'systemd-factory-reset-request.service.in',
diff --git a/units/systemd-factory-reset-esp.service.in b/units/systemd-factory-reset-esp.service.in
new file mode 100644
index 00000000000..84b80fd7537
--- /dev/null
+++ b/units/systemd-factory-reset-esp.service.in
@@ -0,0 +1,28 @@
+# 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.
+
+##########################################################################################
+# TODO: Edit this!
+##########################################################################################
+
+[Unit]
+Description=Delete non-vendor content from the ESP and XBOOTLDR
+Documentation=man:systemd-factory-reset-esp.service(8)
+DefaultDependencies=no
+Requires=factory-reset-now.target
+After=factory-reset-now.target
+Conflicts=shutdown.target
+Before=shutdown.target
+RefuseManualStart=yes
+RefuseManualStop=yes
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart={{LIBEXECDIR}}/systemd-factory-reset complete --retrigger