]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Merge pull request #24878 from keszybz/condition-first-boot
authorLuca Boccassi <bluca@debian.org>
Sat, 1 Oct 2022 18:30:21 +0000 (19:30 +0100)
committerGitHub <noreply@github.com>
Sat, 1 Oct 2022 18:30:21 +0000 (19:30 +0100)
Tweak condition first boot to use the same logic in pid1 and units

man/machine-id.xml
man/systemd.preset.xml
man/systemd.unit.xml
man/systemd.xml
src/core/main.c
src/firstboot/firstboot.c
src/shared/condition.c

index 9bd49582fce12169424b3cfae7b597a82e445c1b..ec1ab64dec13a46205563c29fdb24959628f6da1 100644 (file)
   <refsect1>
     <title>First Boot Semantics</title>
 
-    <para><filename>/etc/machine-id</filename> is used to decide whether a boot is the first one.  The rules
+    <para><filename>/etc/machine-id</filename> is used to decide whether a boot is the first one. The rules
     are as follows:</para>
 
     <orderedlist>
-      <listitem><para>If <filename>/etc/machine-id</filename> does not exist, this is a first boot.  During
-      early boot, <command>systemd</command> will write <literal>uninitialized\n</literal> to this file and overmount
-      a temporary file which contains the actual machine ID.  Later (after <filename>first-boot-complete.target</filename>
-      has been reached), the real machine ID will be written to disk.</para></listitem>
+      <listitem><para>The kernel command argument <varname>systemd.condition-first-boot=</varname> may be
+      used to override the autodetection logic, see
+      <citerefentry><refentrytitle>kernel-command-line</refentrytitle><manvolnum>7</manvolnum></citerefentry>.
+      </para></listitem>
+
+      <listitem><para>Otherwise, if <filename>/etc/machine-id</filename> does not exist, this is a first
+      boot. During early boot, <command>systemd</command> will write <literal>uninitialized\n</literal> to
+      this file and overmount a temporary file which contains the actual machine ID. Later (after
+      <filename>first-boot-complete.target</filename> has been reached), the real machine ID will be written
+      to disk.</para></listitem>
 
       <listitem><para>If <filename>/etc/machine-id</filename> contains the string <literal>uninitialized</literal>,
-      a boot is also considered the first boot.  The same mechanism as above applies.</para></listitem>
+      a boot is also considered the first boot. The same mechanism as above applies.</para></listitem>
 
       <listitem><para>If <filename>/etc/machine-id</filename> exists and is empty, a boot is
-      <emphasis>not</emphasis> considered the first boot.  <command>systemd</command> will still bind-mount a file
+      <emphasis>not</emphasis> considered the first boot. <command>systemd</command> will still bind-mount a file
       containing the actual machine-id over it and later try to commit it to disk (if <filename>/etc/</filename> is
       writable).</para></listitem>
 
       not a first boot.</para></listitem>
     </orderedlist>
 
-    <para>If by any of the above rules, a first boot is detected, units with <varname>ConditionFirstBoot=yes</varname>
-    will be run.</para>
+    <para>If according to the above rules a first boot is detected, units with
+    <varname>ConditionFirstBoot=yes</varname> will be run and <command>systemd</command> will perform
+    additional initialization steps, in particular presetting units.</para>
   </refsect1>
 
   <refsect1>
index 9e6db28536b6dd5ebda9d53bf47dd12dab52950c..ab730d2cc21504a9033b5675e3da6dfac4d29e7a 100644 (file)
     units, but rather centralize them in a distribution or spin default policy, which can be amended by
     administrator policy, see below.</para>
 
-    <para>If no preset files exist, <command>systemctl
-    preset</command> will enable all units that are installed by
-    default. If this is not desired and all units shall rather be
-    disabled, it is necessary to ship a preset file with a single,
-    catchall "<filename>disable *</filename>" line. (See example 1,
-    below.)</para>
+    <para>If no preset files exist, preset operations will enable all units that are installed by default. If
+    this is not desired and all units shall rather be disabled, it is necessary to ship a preset file with a
+    single, catchall "<filename>disable *</filename>" line. (See example 1, below.)</para>
+
+    <para>When the machine is booted for the first time,
+    <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry> will
+    enable/disable all units according to preset policy, similarly to <command>systemctl
+    preset-all</command>. Also see "First Boot Semantics" in
+    <citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
+    </para>
   </refsect1>
 
   <refsect1>
index d502b6da1863671792153cee7f1808bbb7b78243..ebb84e9db871f4d4317181d5c7f3bda8f0678be2 100644 (file)
           <term><varname>ConditionFirstBoot=</varname></term>
 
           <listitem><para>Takes a boolean argument. This condition may be used to conditionalize units on
-          whether the system is booting up for the first time.  This roughly means that <filename>/etc/</filename>
-          is unpopulated (for details, see "First Boot Semantics" in
+          whether the system is booting up for the first time. This roughly means that <filename>/etc/</filename>
+          was unpopulated when the system started booting (for details, see "First Boot Semantics" in
           <citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry>).
-          This may be used to populate <filename>/etc/</filename> on the first boot after factory reset, or
-          when a new system instance boots up for the first time.</para>
+          First boot is considered finished (this condition will evaluate as false) after the manager
+          has finished the startup phase.</para>
+
+          <para>This condition may be used to populate <filename>/etc/</filename> on the first boot after
+          factory reset, or when a new system instance boots up for the first time.</para>
 
           <para>For robustness, units with <varname>ConditionFirstBoot=yes</varname> should order themselves
           before <filename>first-boot-complete.target</filename> and pull in this passive target with
-          <varname>Wants=</varname>.  This ensures that in a case of an aborted first boot, these units will
+          <varname>Wants=</varname>. This ensures that in a case of an aborted first boot, these units will
           be re-run during the next system startup.</para>
 
           <para>If the <varname>systemd.condition-first-boot=</varname> option is specified on the kernel
index f4213e2ee84d4a87e086eb85b160820a396c7455..1a68301d50b806ea136502af3e5c78534ef8a49b 100644 (file)
     <citerefentry><refentrytitle>systemd.special</refentrytitle><manvolnum>7</manvolnum></citerefentry>
     for details about these target units.</para>
 
-    <para>systemd only keeps a minimal set of units loaded into memory. Specifically, the only units that are kept
-    loaded into memory are those for which at least one of the following conditions is true:</para>
+    <para>On first boot, <command>systemd</command> will enable or disable units according to preset policy.
+    See <citerefentry><refentrytitle>systemd.preset</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+    and "First Boot Semantics" in
+    <citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
+
+    <para>systemd only keeps a minimal set of units loaded into memory. Specifically, the only units that are
+    kept loaded into memory are those for which at least one of the following conditions is true:</para>
 
     <orderedlist>
       <listitem><para>It is in an active, activating, deactivating or failed state (i.e. in any unit state except for <literal>inactive</literal>)</para></listitem>
index 27ed8a89ba5bb29b9321c4415cd584dc81ef3985..14a4f81452349c86ddc87da83e6c016ae94657c3 100644 (file)
@@ -2034,6 +2034,8 @@ static int invoke_main_loop(
 }
 
 static void log_execution_mode(bool *ret_first_boot) {
+        bool first_boot = false;
+
         assert(ret_first_boot);
 
         if (arg_system) {
@@ -2050,29 +2052,40 @@ static void log_execution_mode(bool *ret_first_boot) {
 
                 log_info("Detected architecture %s.", architecture_to_string(uname_architecture()));
 
-                if (in_initrd()) {
-                        *ret_first_boot = false;
+                if (in_initrd())
                         log_info("Running in initrd.");
-                else {
+                else {
                         int r;
                         _cleanup_free_ char *id_text = NULL;
 
-                        /* 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.");
+                        /* Let's check whether we are in first boot. First, check if an override was
+                         * specified on the kernel commandline. If yes, we honour that. */
+
+                        r = proc_cmdline_get_bool("systemd.condition-first-boot", &first_boot);
+                        if (r < 0)
+                                log_debug_errno(r, "Failed to parse systemd.condition-first-boot= kernel commandline argument, ignoring: %m");
+
+                        if (r > 0)
+                                log_full(first_boot ? LOG_INFO : LOG_DEBUG,
+                                         "Kernel commandline argument says we are %s first boot.",
+                                         first_boot ? "in" : "not in");
+                        else {
+                                /* Second, perform autodetection. 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 other cases, it is not. This allows container managers and
+                                 * installers to provision a couple of files in /etc but still permit the
+                                 * first-boot initialization to occur. If the container manager wants to
+                                 * provision the machine ID 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");
+
+                                        first_boot = true;
+                                        log_info("Detected first boot.");
+                                } else
+                                        log_debug("Detected initialized system, this is not the first boot.");
                         }
                 }
 
@@ -2092,9 +2105,9 @@ static void log_execution_mode(bool *ret_first_boot) {
                                   arg_action == ACTION_TEST ? " test" : "",
                                   getuid(), strna(t), systemd_features);
                 }
-
-                *ret_first_boot = false;
         }
+
+        *ret_first_boot = first_boot;
 }
 
 static int initialize_runtime(
@@ -2134,7 +2147,7 @@ static int initialize_runtime(
                         (void) os_release_status();
                         (void) hostname_setup(true);
                         /* Force transient machine-id on first boot. */
-                        machine_id_setup(NULL, first_boot, arg_machine_id, NULL);
+                        machine_id_setup(NULL, /* force_transient= */ first_boot, arg_machine_id, NULL);
                         (void) loopback_setup();
                         bump_unix_max_dgram_qlen();
                         bump_file_max_and_nr_open();
index 74fd722e808a816608a65246e7d7c48fbf1cda8b..065ee896cd8cd7dbacce70c5d1e894858bf36128 100644 (file)
@@ -259,8 +259,10 @@ static int prompt_locale(void) {
                 return 0;
         }
 
-        if (!arg_prompt_locale)
+        if (!arg_prompt_locale) {
+                log_debug("Prompting for locale was not requested.");
                 return 0;
+        }
 
         r = get_locales(&locales);
         if (r < 0)
@@ -312,8 +314,11 @@ static int process_locale(void) {
         int r;
 
         etc_localeconf = prefix_roota(arg_root, "/etc/locale.conf");
-        if (laccess(etc_localeconf, F_OK) >= 0 && !arg_force)
+        if (laccess(etc_localeconf, F_OK) >= 0 && !arg_force) {
+                log_debug("Found %s, assuming locale information has been configured.",
+                          etc_localeconf);
                 return 0;
+        }
 
         if (arg_copy_locale && arg_root) {
 
@@ -366,12 +371,14 @@ static int prompt_keymap(void) {
                 return 0;
         }
 
-        if (!arg_prompt_keymap)
+        if (!arg_prompt_keymap) {
+                log_debug("Prompting for keymap was not requested.");
                 return 0;
+        }
 
         r = get_keymaps(&kmaps);
         if (r == -ENOENT) /* no keymaps installed */
-                return r;
+                return log_debug_errno(r, "No keymaps are installed.");
         if (r < 0)
                 return log_error_errno(r, "Failed to read keymaps: %m");
 
@@ -387,8 +394,11 @@ static int process_keymap(void) {
         int r;
 
         etc_vconsoleconf = prefix_roota(arg_root, "/etc/vconsole.conf");
-        if (laccess(etc_vconsoleconf, F_OK) >= 0 && !arg_force)
+        if (laccess(etc_vconsoleconf, F_OK) >= 0 && !arg_force) {
+                log_debug("Found %s, assuming console has been configured.",
+                          etc_vconsoleconf);
                 return 0;
+        }
 
         if (arg_copy_keymap && arg_root) {
 
@@ -445,8 +455,10 @@ static int prompt_timezone(void) {
                 return 0;
         }
 
-        if (!arg_prompt_timezone)
+        if (!arg_prompt_timezone) {
+                log_debug("Prompting for timezone was not requested.");
                 return 0;
+        }
 
         r = get_timezones(&zones);
         if (r < 0)
@@ -467,8 +479,11 @@ static int process_timezone(void) {
         int r;
 
         etc_localtime = prefix_roota(arg_root, "/etc/localtime");
-        if (laccess(etc_localtime, F_OK) >= 0 && !arg_force)
+        if (laccess(etc_localtime, F_OK) >= 0 && !arg_force) {
+                log_debug("Found %s, assuming timezone has been configured.",
+                          etc_localtime);
                 return 0;
+        }
 
         if (arg_copy_timezone && arg_root) {
                 _cleanup_free_ char *p = NULL;
@@ -512,8 +527,10 @@ static int prompt_hostname(void) {
         if (arg_hostname)
                 return 0;
 
-        if (!arg_prompt_hostname)
+        if (!arg_prompt_hostname) {
+                log_debug("Prompting for hostname was not requested.");
                 return 0;
+        }
 
         print_welcome();
         putchar('\n');
@@ -549,8 +566,11 @@ static int process_hostname(void) {
         int r;
 
         etc_hostname = prefix_roota(arg_root, "/etc/hostname");
-        if (laccess(etc_hostname, F_OK) >= 0 && !arg_force)
+        if (laccess(etc_hostname, F_OK) >= 0 && !arg_force) {
+                log_debug("Found %s, assuming hostname has been configured.",
+                          etc_hostname);
                 return 0;
+        }
 
         r = prompt_hostname();
         if (r < 0)
@@ -574,11 +594,16 @@ static int process_machine_id(void) {
         int r;
 
         etc_machine_id = prefix_roota(arg_root, "/etc/machine-id");
-        if (laccess(etc_machine_id, F_OK) >= 0 && !arg_force)
+        if (laccess(etc_machine_id, F_OK) >= 0 && !arg_force) {
+                log_debug("Found %s, assuming machine-id has been configured.",
+                          etc_machine_id);
                 return 0;
+        }
 
-        if (sd_id128_is_null(arg_machine_id))
+        if (sd_id128_is_null(arg_machine_id)) {
+                log_debug("Initialization of machine-id was not requested, skipping.");
                 return 0;
+        }
 
         r = write_string_file(etc_machine_id, SD_ID128_TO_STRING(arg_machine_id),
                               WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_SYNC | WRITE_STRING_FILE_MKDIR_0755 |
@@ -600,8 +625,10 @@ static int prompt_root_password(void) {
         if (get_credential_user_password("root", &arg_root_password, &arg_root_password_is_hashed) >= 0)
                 return 0;
 
-        if (!arg_prompt_root_password)
+        if (!arg_prompt_root_password) {
+                log_debug("Prompting for root password was not requested.");
                 return 0;
+        }
 
         print_welcome();
         putchar('\n');
@@ -684,8 +711,10 @@ static int prompt_root_shell(void) {
                 return 0;
         }
 
-        if (!arg_prompt_root_shell)
+        if (!arg_prompt_root_shell) {
+                log_debug("Prompting for root shell was not requested.");
                 return 0;
+        }
 
         print_welcome();
         putchar('\n');
@@ -850,7 +879,7 @@ static int write_root_shadow(const char *shadow_path, const char *hashed_passwor
         return 0;
 }
 
-static int process_root_args(void) {
+static int process_root_account(void) {
         _cleanup_close_ int lock = -1;
         _cleanup_(erase_and_freep) char *_hashed_password = NULL;
         const char *password, *hashed_password;
@@ -860,13 +889,18 @@ static int process_root_args(void) {
         etc_passwd = prefix_roota(arg_root, "/etc/passwd");
         etc_shadow = prefix_roota(arg_root, "/etc/shadow");
 
-        if (laccess(etc_passwd, F_OK) >= 0 && laccess(etc_shadow, F_OK) >= 0 && !arg_force)
+        if (laccess(etc_passwd, F_OK) >= 0 && laccess(etc_shadow, F_OK) >= 0 && !arg_force) {
+                log_debug("Found %s and %s, assuming root account has been initialized.",
+                          etc_passwd, etc_shadow);
                 return 0;
+        }
 
         /* Don't create/modify passwd and shadow if not asked */
         if (!(arg_root_password || arg_prompt_root_password || arg_copy_root_password || arg_delete_root_password ||
-              arg_root_shell || arg_prompt_root_shell || arg_copy_root_shell))
+              arg_root_shell || arg_prompt_root_shell || arg_copy_root_shell)) {
+                log_debug("Initialization of root account was not requested, skipping.");
                 return 0;
+        }
 
         (void) mkdir_parents(etc_passwd, 0755);
 
@@ -945,11 +979,16 @@ static int process_kernel_cmdline(void) {
         int r;
 
         etc_kernel_cmdline = prefix_roota(arg_root, "/etc/kernel/cmdline");
-        if (laccess(etc_kernel_cmdline, F_OK) >= 0 && !arg_force)
+        if (laccess(etc_kernel_cmdline, F_OK) >= 0 && !arg_force) {
+                log_debug("Found %s, assuming kernel has been configured.",
+                          etc_kernel_cmdline);
                 return 0;
+        }
 
-        if (!arg_kernel_cmdline)
+        if (!arg_kernel_cmdline) {
+                log_debug("Creation of /etc/kernel/cmdline was not requested, skipping.");
                 return 0;
+        }
 
         r = write_string_file(etc_kernel_cmdline, arg_kernel_cmdline,
                               WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_SYNC | WRITE_STRING_FILE_MKDIR_0755 |
@@ -1332,8 +1371,10 @@ static int run(int argc, char *argv[]) {
                 r = proc_cmdline_get_bool("systemd.firstboot", &enabled);
                 if (r < 0)
                         return log_error_errno(r, "Failed to parse systemd.firstboot= kernel command line argument, ignoring: %m");
-                if (r > 0 && !enabled)
+                if (r > 0 && !enabled) {
+                        log_debug("Found systemd.firstboot=no kernel command line argument, terminating.");
                         return 0; /* disabled */
+                }
         }
 
         if (arg_image) {
@@ -1377,7 +1418,7 @@ static int run(int argc, char *argv[]) {
         if (r < 0)
                 return r;
 
-        r = process_root_args();
+        r = process_root_account();
         if (r < 0)
                 return r;
 
index ffca2006c0757560537a4780528ee158c7ed7c62..aa34e1e2853d33df28f6f20e694e936ab1700dbc 100644 (file)
@@ -824,27 +824,20 @@ static int condition_test_needs_update(Condition *c, char **env) {
 
 static int condition_test_first_boot(Condition *c, char **env) {
         int r, q;
-        bool b;
 
         assert(c);
         assert(c->parameter);
         assert(c->type == CONDITION_FIRST_BOOT);
 
-        r = proc_cmdline_get_bool("systemd.condition-first-boot", &b);
-        if (r < 0)
-                log_debug_errno(r, "Failed to parse systemd.condition-first-boot= kernel command line argument, ignoring: %m");
-        if (r > 0)
-                return b == !!r;
-
         r = parse_boolean(c->parameter);
         if (r < 0)
                 return r;
 
         q = access("/run/systemd/first-boot", F_OK);
         if (q < 0 && errno != ENOENT)
-                log_debug_errno(errno, "Failed to check if /run/systemd/first-boot exists, ignoring: %m");
+                log_debug_errno(errno, "Failed to check if /run/systemd/first-boot exists, assuming no: %m");
 
-        return (q >= 0) == !!r;
+        return (q >= 0) == r;
 }
 
 static int condition_test_environment(Condition *c, char **env) {