From 7139e9d9a3857724a857615a4bd80f857685eb97 Mon Sep 17 00:00:00 2001 From: Harald Seiler Date: Sun, 6 Sep 2020 21:23:36 +0200 Subject: [PATCH 01/12] preset: don't enable proc-sys-fs-binfmt_misc.mount The proc-sys-fs-binfmt_misc.mount unit should not be enabled by preset-all because it should only be used as fallback in case proc-sys-fs-binfmt_misc.automount cannot be used on a system. In these cases it should be enabled manually by an administrator. --- presets/90-systemd.preset | 1 + 1 file changed, 1 insertion(+) diff --git a/presets/90-systemd.preset b/presets/90-systemd.preset index dd268ae1b5..9e430904f1 100644 --- a/presets/90-systemd.preset +++ b/presets/90-systemd.preset @@ -37,6 +37,7 @@ disable systemd-networkd-wait-online.service disable systemd-time-wait-sync.service disable systemd-boot-check-no-failures.service disable systemd-network-generator.service +disable proc-sys-fs-binfmt_misc.mount disable syslog.socket From 583cef3b7347c4e6ca269d38efef6d2e4314aba6 Mon Sep 17 00:00:00 2001 From: Harald Seiler Date: Sun, 6 Sep 2020 21:23:35 +0200 Subject: [PATCH 02/12] core: treat "uninitialized" in /etc/machine-id as first boot as well When /etc/machine-id contains the string "uninitialized" instead of a valid machine-id, treat this like the file was missing and mark this boot as the first (-> units with ConditionFirstBoot=yes will run). --- src/core/main.c | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/core/main.c b/src/core/main.c index 6cd596fa79..a8a48db2a0 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -2001,15 +2001,26 @@ static void log_execution_mode(bool *ret_first_boot) { *ret_first_boot = false; log_info("Running in initial RAM disk."); } else { - /* Let's check whether we are in first boot, i.e. whether /etc is still unpopulated. We use - * /etc/machine-id as flag file, for this: if it exists we assume /etc is populated, if it - * doesn't it's unpopulated. This allows container managers and installers to provision a - * couple of files already. If the container manager wants to provision the machine ID itself - * it should pass $container_uuid to PID 1. */ + int r; + _cleanup_free_ char *id_text = NULL; - *ret_first_boot = access("/etc/machine-id", F_OK) < 0; - if (*ret_first_boot) - log_info("Running with unpopulated /etc."); + /* Let's check whether we are in first boot. We use /etc/machine-id as flag file + * for this: If it is missing or contains the value "uninitialized", this is the + * first boot. In any other case, it is not. This allows container managers and + * installers to provision a couple of files already. If the container manager + * wants to provision the machine ID itself it should pass $container_uuid to PID 1. */ + + r = read_one_line_file("/etc/machine-id", &id_text); + if (r < 0 || streq(id_text, "uninitialized")) { + if (r < 0 && r != -ENOENT) + log_warning_errno(r, "Unexpected error while reading /etc/machine-id, ignoring: %m"); + + *ret_first_boot = true; + log_info("Detected first boot."); + } else { + *ret_first_boot = false; + log_debug("Detected initialized system, this is not the first boot."); + } } } else { if (DEBUG_LOGGING) { From 8085114828c3b07406298f0fa89d368413978e20 Mon Sep 17 00:00:00 2001 From: Harald Seiler Date: Sun, 6 Sep 2020 21:23:36 +0200 Subject: [PATCH 03/12] id128: add format which treats "uninitialized" like an empty id Add a new ID128_PLAIN_OR_UNINIT format which treats the string "uninitialized" like the file was empty and return -ENOMEDIUM. This format should be used when reading an /etc/machine-id file from an image that is not currently running. --- src/libsystemd/sd-id128/id128-util.c | 8 +++++++- src/libsystemd/sd-id128/id128-util.h | 4 ++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/libsystemd/sd-id128/id128-util.c b/src/libsystemd/sd-id128/id128-util.c index 335f22b920..ebbfb2d32e 100644 --- a/src/libsystemd/sd-id128/id128-util.c +++ b/src/libsystemd/sd-id128/id128-util.c @@ -10,6 +10,7 @@ #include "id128-util.h" #include "io-util.h" #include "stdio-util.h" +#include "string-util.h" char *id128_to_uuid_string(sd_id128_t id, char s[static ID128_UUID_STRING_MAX]) { unsigned n, k = 0; @@ -97,6 +98,11 @@ int id128_read_fd(int fd, Id128Format f, sd_id128_t *ret) { switch (l) { + case 13: + case 14: + /* Treat an "uninitialized" id file like an empty one */ + return f == ID128_PLAIN_OR_UNINIT && strneq(buffer, "uninitialized\n", l) ? -ENOMEDIUM : -EINVAL; + case 33: /* plain UUID with trailing newline */ if (buffer[32] != '\n') return -EINVAL; @@ -115,7 +121,7 @@ int id128_read_fd(int fd, Id128Format f, sd_id128_t *ret) { _fallthrough_; case 36: /* RFC UUID without trailing newline */ - if (f == ID128_PLAIN) + if (IN_SET(f, ID128_PLAIN, ID128_PLAIN_OR_UNINIT)) return -EINVAL; buffer[36] = 0; diff --git a/src/libsystemd/sd-id128/id128-util.h b/src/libsystemd/sd-id128/id128-util.h index 1901bf119f..1453c00f2f 100644 --- a/src/libsystemd/sd-id128/id128-util.h +++ b/src/libsystemd/sd-id128/id128-util.h @@ -17,6 +17,10 @@ bool id128_is_valid(const char *s) _pure_; typedef enum Id128Format { ID128_ANY, ID128_PLAIN, /* formatted as 32 hex chars as-is */ + ID128_PLAIN_OR_UNINIT, /* formatted as 32 hex chars as-is; allow special "uninitialized" + * value when reading from file (id128_read() and id128_read_fd()). + * + * This format should be used when reading a machine-id file. */ ID128_UUID, /* formatted as 36 character uuid string */ _ID128_FORMAT_MAX, } Id128Format; From c5fbeedb0c9e154ddde7ad47b48b469dfc835d02 Mon Sep 17 00:00:00 2001 From: Harald Seiler Date: Thu, 15 Oct 2020 21:22:15 +0200 Subject: [PATCH 04/12] nspawn: robustly deal with "uninitialized" machine-id When nspawn starts an image, this image could be in any state, including an aborted first boot. For this case, it needs to correctly handle the situation like there was no machine-id at all. --- src/nspawn/nspawn.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 82bb167397..5db08cb5b3 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -2726,7 +2726,7 @@ static int setup_machine_id(const char *directory) { etc_machine_id = prefix_roota(directory, "/etc/machine-id"); - r = id128_read(etc_machine_id, ID128_PLAIN, &id); + r = id128_read(etc_machine_id, ID128_PLAIN_OR_UNINIT, &id); if (r < 0) { if (!IN_SET(r, -ENOENT, -ENOMEDIUM)) /* If the file is missing or empty, we don't mind */ return log_error_errno(r, "Failed to read machine ID from container image: %m"); From 448b782cb2629f303f0c979f67e97411073e19cc Mon Sep 17 00:00:00 2001 From: Harald Seiler Date: Thu, 15 Oct 2020 21:25:06 +0200 Subject: [PATCH 05/12] repart: correctly handle "uninitialized" machine-id When systemd-repart runs from initramfs, it reads out /etc/machine-id from the rootfs as a seed for partition UUIDs. However, the machine-id could be in an "uninitialized" state from a previous failed first boot. In this situation the -ENOMEDIUM code-path (no machine-id set) should be taken. --- src/partition/repart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/partition/repart.c b/src/partition/repart.c index d406c9dbe3..4cf6a5fe3a 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -3245,7 +3245,7 @@ static int context_read_seed(Context *context, const char *root) { else if (fd < 0) return log_error_errno(fd, "Failed to determine machine ID of image: %m"); else { - r = id128_read_fd(fd, ID128_PLAIN, &context->seed); + r = id128_read_fd(fd, ID128_PLAIN_OR_UNINIT, &context->seed); if (r == -ENOMEDIUM) log_info("No machine ID set, using randomized partition UUIDs."); else if (r < 0) From ab763cb2be08410d45dafed9c2801406a427a4d3 Mon Sep 17 00:00:00 2001 From: Harald Seiler Date: Sun, 6 Sep 2020 21:23:36 +0200 Subject: [PATCH 06/12] dissect-image: support "uninitialized" machine-id If the first boot was aborted, /etc/machine-id might read as "uninitialized" in some cases. Add a separate case for this instead of printing a confusing error message. --- src/shared/dissect-image.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 91120d7219..cbd3357219 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -2148,6 +2148,8 @@ int dissected_image_acquire_metadata(DissectedImage *m) { log_debug_errno(r, "Image contains invalid /etc/machine-id: %s", line); } else if (r == 0) log_debug("/etc/machine-id file is empty."); + else if (streq(line, "uninitialized")) + log_debug("/etc/machine-id file is uninitialized (likely aborted first boot)."); else log_debug("/etc/machine-id has unexpected length %i.", r); From 3023f2fead7df9f219acf3c8595d974e31ad23cc Mon Sep 17 00:00:00 2001 From: Harald Seiler Date: Sun, 6 Sep 2020 21:23:36 +0200 Subject: [PATCH 07/12] core: keep machine-id transient until first boot completes Currently, a loss of power after the machine-id was written but before all units with ConditionFirstBoot=yes ran would lead to the next boot finding a valid machine-id, thus not being marked first boot and not re-running these units. To make the first boot mechanism more robust, instead of writing /etc/machine-id very early, fill it with a marker value "uninitialized" and overmount it with a transiently provisioned machine-id. Then, after the first boots completes (when systemd-machine-id-commit.service runs), write the real machine-id to disk. This mechanism is of course only invoked on first boot. If a first boot is not detected, the machine-id is handled as previously. Fixes: #4511 --- src/core/machine-id-setup.c | 29 ++++++++++++++++---- src/core/machine-id-setup.h | 4 ++- src/core/main.c | 5 +++- src/machine-id-setup/machine-id-setup-main.c | 2 +- 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/core/machine-id-setup.c b/src/core/machine-id-setup.c index 6aecd36fe6..4e9a8d266e 100644 --- a/src/core/machine-id-setup.c +++ b/src/core/machine-id-setup.c @@ -11,6 +11,7 @@ #include "fd-util.h" #include "fs-util.h" #include "id128-util.h" +#include "io-util.h" #include "log.h" #include "machine-id-setup.h" #include "macro.h" @@ -86,7 +87,7 @@ static int generate_machine_id(const char *root, sd_id128_t *ret) { return 0; } -int machine_id_setup(const char *root, sd_id128_t machine_id, sd_id128_t *ret) { +int machine_id_setup(const char *root, bool force_transient, sd_id128_t machine_id, sd_id128_t *ret) { const char *etc_machine_id, *run_machine_id; _cleanup_close_ int fd = -1; bool writable; @@ -143,13 +144,31 @@ int machine_id_setup(const char *root, sd_id128_t machine_id, sd_id128_t *ret) { if (ftruncate(fd, 0) < 0) return log_error_errno(errno, "Failed to truncate %s: %m", etc_machine_id); - if (id128_write_fd(fd, ID128_PLAIN, machine_id, true) >= 0) - goto finish; + /* If the caller requested a transient machine-id, write the string "uninitialized\n" to + * disk and overmount it with a transient file. + * + * Otherwise write the machine-id directly to disk. */ + if (force_transient) { + r = loop_write(fd, "uninitialized\n", strlen("uninitialized\n"), false); + if (r < 0) + return log_error_errno(r, "Failed to write uninitialized %s: %m", etc_machine_id); + + r = fsync_full(fd); + if (r < 0) + return log_error_errno(r, "Failed to sync %s: %m", etc_machine_id); + } else { + r = id128_write_fd(fd, ID128_PLAIN, machine_id, true); + if (r < 0) + return log_error_errno(r, "Failed to write %s: %m", etc_machine_id); + else + goto finish; + } } fd = safe_close(fd); - /* Hmm, we couldn't write it? So let's write it to /run/machine-id as a replacement */ + /* Hmm, we couldn't or shouldn't write the machine-id to /etc? + * So let's write it to /run/machine-id as a replacement */ run_machine_id = prefix_roota(root, "/run/machine-id"); @@ -167,7 +186,7 @@ int machine_id_setup(const char *root, sd_id128_t machine_id, sd_id128_t *ret) { return r; } - log_info("Installed transient %s file.", etc_machine_id); + log_full(force_transient ? LOG_DEBUG : LOG_INFO, "Installed transient %s file.", etc_machine_id); /* Mark the mount read-only */ r = mount_follow_verbose(LOG_WARNING, NULL, etc_machine_id, NULL, MS_BIND|MS_RDONLY|MS_REMOUNT, NULL); diff --git a/src/core/machine-id-setup.h b/src/core/machine-id-setup.h index d6ac62a882..e207ccf9c1 100644 --- a/src/core/machine-id-setup.h +++ b/src/core/machine-id-setup.h @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1+ */ #pragma once +#include + int machine_id_commit(const char *root); -int machine_id_setup(const char *root, sd_id128_t requested, sd_id128_t *ret); +int machine_id_setup(const char *root, bool force_transient, sd_id128_t requested, sd_id128_t *ret); diff --git a/src/core/main.c b/src/core/main.c index a8a48db2a0..728e1578b1 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -2037,6 +2037,7 @@ static void log_execution_mode(bool *ret_first_boot) { static int initialize_runtime( bool skip_setup, + bool first_boot, struct rlimit *saved_rlimit_nofile, struct rlimit *saved_rlimit_memlock, const char **ret_error_message) { @@ -2070,7 +2071,8 @@ static int initialize_runtime( status_welcome(); hostname_setup(); - machine_id_setup(NULL, arg_machine_id, NULL); + /* Force transient machine-id on first boot. */ + machine_id_setup(NULL, first_boot, arg_machine_id, NULL); (void) loopback_setup(); bump_unix_max_dgram_qlen(); bump_file_max_and_nr_open(); @@ -2798,6 +2800,7 @@ int main(int argc, char *argv[]) { log_execution_mode(&first_boot); r = initialize_runtime(skip_setup, + first_boot, &saved_rlimit_nofile, &saved_rlimit_memlock, &error_message); diff --git a/src/machine-id-setup/machine-id-setup-main.c b/src/machine-id-setup/machine-id-setup-main.c index 872b00c158..2bcd99109e 100644 --- a/src/machine-id-setup/machine-id-setup-main.c +++ b/src/machine-id-setup/machine-id-setup-main.c @@ -128,7 +128,7 @@ static int run(int argc, char *argv[]) { if (r < 0) return log_error_errno(r, "Failed to read machine ID back: %m"); } else { - r = machine_id_setup(arg_root, SD_ID128_NULL, &id); + r = machine_id_setup(arg_root, false, SD_ID128_NULL, &id); if (r < 0) return r; } From c261a5d01469a3f5b6c448c066ea7ad6d14a5300 Mon Sep 17 00:00:00 2001 From: Harald Seiler Date: Sun, 6 Sep 2020 21:35:33 +0200 Subject: [PATCH 08/12] machine-id-setup: sync before committing machine-id sync() before committing a transient machine-id to disk. This will ensure that any filesystem changes made by first-boot units will have been persisted before the first boot is marked as completed. --- src/core/machine-id-setup.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/core/machine-id-setup.c b/src/core/machine-id-setup.c index 4e9a8d266e..c55e0b5321 100644 --- a/src/core/machine-id-setup.c +++ b/src/core/machine-id-setup.c @@ -202,10 +202,22 @@ finish: int machine_id_commit(const char *root) { _cleanup_close_ int fd = -1, initial_mntns_fd = -1; - const char *etc_machine_id; + const char *etc_machine_id, *sync_path; sd_id128_t id; int r; + /* Before doing anything, sync everything to ensure any changes by first-boot units are persisted. + * + * First, explicitly sync the file systems we care about and check if it worked. */ + FOREACH_STRING(sync_path, "/etc/", "/var/") { + r = syncfs_path(AT_FDCWD, sync_path); + if (r < 0) + return log_error_errno(r, "Cannot sync %s: %m", sync_path); + } + + /* Afterwards, sync() the rest too, but we can't check the return value for these. */ + sync(); + /* Replaces a tmpfs bind mount of /etc/machine-id by a proper file, atomically. For this, the umount is removed * in a mount namespace, a new file is created at the right place. Afterwards the mount is also removed in the * original mount namespace, thus revealing the file that was just created. */ From f4466bdbf9c39e9b303809f93c04a80795606503 Mon Sep 17 00:00:00 2001 From: Harald Seiler Date: Sun, 6 Sep 2020 21:23:36 +0200 Subject: [PATCH 09/12] units: add first-boot-complete.target for first boot ordering Add a new target for synchronizing units that wish to run once during the first boot of the system. The machine-id will be committed to disk only after the target has been reached, thus ensuring that all units ordered before it had a chance to complete. --- man/systemd.special.xml | 12 ++++++++++++ units/first-boot-complete.target | 14 ++++++++++++++ units/meson.build | 1 + units/systemd-machine-id-commit.service | 4 ++-- 4 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 units/first-boot-complete.target diff --git a/man/systemd.special.xml b/man/systemd.special.xml index e43d765c9f..1434ead7d5 100644 --- a/man/systemd.special.xml +++ b/man/systemd.special.xml @@ -32,6 +32,7 @@ emergency.target, exit.target, final.target, + first-boot-complete.target, getty.target, getty-pre.target, graphical.target, @@ -878,6 +879,17 @@ stopped. + + first-boot-complete.target + + This passive target is intended as a synchronization point for units that need to run once + during the first boot. Only after all units ordered before this target have finished, will the + machine-id5 + be committed to disk, marking the first boot as completed. If the boot is aborted at any time + before that, the next boot will re-run any units with ConditionFirstBoot=yes. + + + getty-pre.target diff --git a/units/first-boot-complete.target b/units/first-boot-complete.target new file mode 100644 index 0000000000..98b236e9ff --- /dev/null +++ b/units/first-boot-complete.target @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: LGPL-2.1+ +# +# 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. + +[Unit] +Description=First Boot Complete +Documentation=man:systemd.special(7) +RefuseManualStart=yes +ConditionFirstBoot=yes diff --git a/units/meson.build b/units/meson.build index e94e7f7efd..40ffca5058 100644 --- a/units/meson.build +++ b/units/meson.build @@ -17,6 +17,7 @@ units = [ ['emergency.target', ''], ['exit.target', ''], ['final.target', ''], + ['first-boot-complete.target', ''], ['getty.target', '', 'multi-user.target.wants/'], ['getty-pre.target', ''], diff --git a/units/systemd-machine-id-commit.service b/units/systemd-machine-id-commit.service index e3acb0f326..5616a35701 100644 --- a/units/systemd-machine-id-commit.service +++ b/units/systemd-machine-id-commit.service @@ -12,8 +12,8 @@ Description=Commit a transient machine-id on disk Documentation=man:systemd-machine-id-commit.service(8) DefaultDependencies=no Conflicts=shutdown.target -Before=sysinit.target shutdown.target -After=local-fs.target +Before=shutdown.target +After=local-fs.target first-boot-complete.target ConditionPathIsReadWrite=/etc ConditionPathIsMountPoint=/etc/machine-id From a1e378714869fcda7ce30df81a8b5f1e47636ad7 Mon Sep 17 00:00:00 2001 From: Harald Seiler Date: Sun, 6 Sep 2020 21:23:36 +0200 Subject: [PATCH 10/12] units: order systemd-firstboot.service before first-boot-complete.target Make sure systemd-firstboot completes before reaching first-boot-complete.target and thus marking the first boot as completed. This way, it is guaranteed that systemd-firstboot has a chance to complete provisioning at least once, even in cases of the first boot getting aborted early. --- units/systemd-firstboot.service | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/units/systemd-firstboot.service b/units/systemd-firstboot.service index 9f5c7101cd..e17fef7b36 100644 --- a/units/systemd-firstboot.service +++ b/units/systemd-firstboot.service @@ -13,7 +13,8 @@ Documentation=man:systemd-firstboot(1) DefaultDependencies=no Conflicts=shutdown.target After=systemd-remount-fs.service -Before=systemd-sysusers.service sysinit.target shutdown.target +Before=systemd-sysusers.service sysinit.target first-boot-complete.target shutdown.target +Wants=first-boot-complete.target ConditionPathIsReadWrite=/etc ConditionFirstBoot=yes From 3af54f9bbe9b7971ec196a85351535d20b51b766 Mon Sep 17 00:00:00 2001 From: Harald Seiler Date: Sun, 6 Sep 2020 21:43:57 +0200 Subject: [PATCH 11/12] units: order systemd-random-seed.service before first-boot-complete.target Ensure that systemd-random-seed.service has completed before marking a first boot as completed to guarantee that a saved seed will only be used after it has been initialized at least once. --- units/systemd-random-seed.service.in | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/units/systemd-random-seed.service.in b/units/systemd-random-seed.service.in index f97f99a966..aa4b3e7357 100644 --- a/units/systemd-random-seed.service.in +++ b/units/systemd-random-seed.service.in @@ -14,7 +14,8 @@ DefaultDependencies=no RequiresMountsFor=@RANDOM_SEED@ Conflicts=shutdown.target After=systemd-remount-fs.service -Before=shutdown.target +Before=first-boot-complete.target shutdown.target +Wants=first-boot-complete.target ConditionVirtualization=!container [Service] From a48627ef87e855db7a18400a4f2a63c90f6e60fe Mon Sep 17 00:00:00 2001 From: Harald Seiler Date: Sun, 6 Sep 2020 22:57:59 +0200 Subject: [PATCH 12/12] man: Document new machine-id and first boot behavior --- man/machine-id.xml | 37 +++++++++++++++++++++++++++++++++---- man/systemd.unit.xml | 14 ++++++++++---- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/man/machine-id.xml b/man/machine-id.xml index cf759f2a0e..7e889ca47a 100644 --- a/man/machine-id.xml +++ b/man/machine-id.xml @@ -82,10 +82,11 @@ For operating system images which are created once and used on multiple machines, for example for containers or in the cloud, - /etc/machine-id should be an empty file in the generic file - system image. An ID will be generated during boot and saved to this file if - possible. Having an empty file in place is useful because it allows a temporary file - to be bind-mounted over the real file, in case the image is used read-only. + /etc/machine-id should be either missing or an empty file in the generic file + system image (the difference between the two options is described under "First Boot Semantics" below). An + ID will be generated during boot and saved to this file if possible. Having an empty file in place is + useful because it allows a temporary file to be bind-mounted over the real file, in case the image is + used read-only. systemd-firstboot1 may be used to initialize /etc/machine-id on mounted (but not @@ -115,6 +116,34 @@ early boot but become writable later on. + + First Boot Semantics + + /etc/machine-id is used to decide whether a boot is the first one. The rules + are as follows: + + + If /etc/machine-id does not exist, this is a first boot. During + early boot, systemd will write unitialized\n to this file and overmount + a temporary file which contains the actual machine ID. Later (after first-boot-complete.target + has been reached), the real machine ID will be written to disk. + + If /etc/machine-id contains the string uninitialized, + a boot is also considered the first boot. The same mechanism as above applies. + + If /etc/machine-id exists and is empty, a boot is + not considered the first boot. systemd will still bind-mount a file + containing the actual machine-id over it and later try to commit it to disk (if /etc/ is + writable). + + If /etc/machine-id already contains a valid machine-id, this is + not a first boot. + + + If by any of the above rules, a first boot is detected, units with ConditionFirstBoot=yes + will be run. + + Relation to OSF UUIDs diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml index 27035798bc..4b8e515fe2 100644 --- a/man/systemd.unit.xml +++ b/man/systemd.unit.xml @@ -1315,10 +1315,16 @@ ConditionFirstBoot= Takes a boolean argument. This condition may be used to conditionalize units on - whether the system is booting up with an unpopulated /etc/ directory - (specifically: an /etc/ with no /etc/machine-id). This may - be used to populate /etc/ on the first boot after factory reset, or when a new - system instance boots up for the first time. + whether the system is booting up for the first time. This roughly means that /etc/ + is unpopulated (for details, see "First Boot Semantics" in + machine-id5). + This may be used to populate /etc/ on the first boot after factory reset, or + when a new system instance boots up for the first time. + + For robustness, units with ConditionFirstBoot=yes should order themselves + before first-boot-complete.target and pull in this passive target with + Wants=. This ensures that in a case of an aborted first boot, these units will + be re-run during the next system startup. If the systemd.condition-first-boot= option is specified on the kernel command line (taking a boolean), it will override the result of this condition check, taking