diff --git a/man/machine-id.xml b/man/machine-id.xml index 9bd49582fc..ec1ab64dec 100644 --- a/man/machine-id.xml +++ b/man/machine-id.xml @@ -119,20 +119,26 @@ First Boot Semantics - /etc/machine-id is used to decide whether a boot is the first one. The rules + /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 uninitialized\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. + The kernel command argument systemd.condition-first-boot= may be + used to override the autodetection logic, see + kernel-command-line7. + + + Otherwise, if /etc/machine-id does not exist, this is a first + boot. During early boot, systemd will write uninitialized\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. + 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 + 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). @@ -140,8 +146,9 @@ not a first boot. - If by any of the above rules, a first boot is detected, units with ConditionFirstBoot=yes - will be run. + If according to the above rules a first boot is detected, units with + ConditionFirstBoot=yes will be run and systemd will perform + additional initialization steps, in particular presetting units. diff --git a/man/systemd.preset.xml b/man/systemd.preset.xml index 9e6db28536..ab730d2cc2 100644 --- a/man/systemd.preset.xml +++ b/man/systemd.preset.xml @@ -47,12 +47,16 @@ units, but rather centralize them in a distribution or spin default policy, which can be amended by administrator policy, see below. - If no preset files exist, systemctl - preset 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 "disable *" line. (See example 1, - below.) + 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 "disable *" line. (See example 1, below.) + + When the machine is booted for the first time, + systemd1 will + enable/disable all units according to preset policy, similarly to systemctl + preset-all. Also see "First Boot Semantics" in + machine-id5. + diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml index d502b6da18..ebb84e9db8 100644 --- a/man/systemd.unit.xml +++ b/man/systemd.unit.xml @@ -1455,15 +1455,18 @@ ConditionFirstBoot= 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 /etc/ - is unpopulated (for details, see "First Boot Semantics" in + whether the system is booting up for the first time. This roughly means that /etc/ + was unpopulated when the system started booting (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. + First boot is considered finished (this condition will evaluate as false) after the manager + has finished the startup phase. + + This condition 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 + 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 diff --git a/man/systemd.xml b/man/systemd.xml index f4213e2ee8..1a68301d50 100644 --- a/man/systemd.xml +++ b/man/systemd.xml @@ -188,8 +188,13 @@ systemd.special7 for details about these target units. - 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: + On first boot, systemd will enable or disable units according to preset policy. + See systemd.preset5 + and "First Boot Semantics" in + machine-id5. + + 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: It is in an active, activating, deactivating or failed state (i.e. in any unit state except for inactive) diff --git a/src/core/main.c b/src/core/main.c index 27ed8a89ba..14a4f81452 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -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. */ + /* 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 = 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"); + 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"); - *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."); + 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(); diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index 74fd722e80..065ee896cd 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -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; diff --git a/src/shared/condition.c b/src/shared/condition.c index ffca2006c0..aa34e1e285 100644 --- a/src/shared/condition.c +++ b/src/shared/condition.c @@ -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) {