have been provided. This clears the way for a new
"systemd-sysupdate@.service" unit for varlink activation of sysupdate.
+ Changes in systemd-firstboot:
+
+ * The "systemd.firstboot=" kernel command line option now accepts the
+ special value "headless" in addition to a boolean. Like "no", it
+ suppresses all interactive prompts, but unlike "no" it still performs
+ non-interactive auto-configuration that requires no user input (such
+ as selecting the sole installed locale, or applying settings provided
+ via credentials). This is useful for unattended installations that
+ should be provisioned as far as possible without ever blocking on a
+ prompt.
+
CHANGES WITH 261:
Announcements of Future Feature Removals and Incompatible Changes:
<varlistentry>
<term><varname>systemd.firstboot=</varname></term>
- <listitem><para>Takes a boolean argument, defaults to on. If off,
+ <listitem><para>Takes a boolean argument or the special value <literal>headless</literal>, defaults to
+ on. If off,
<citerefentry><refentrytitle>systemd-firstboot.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
and
<citerefentry><refentrytitle>systemd-homed-firstboot.service</refentrytitle><manvolnum>1</manvolnum></citerefentry>
will not query the user for basic system settings, even if the system boots up for the first time and
- the relevant settings are not initialized yet. Not to be confused with
- <varname>systemd.condition_first_boot=</varname> (see below), which overrides the result of the
- <varname>ConditionFirstBoot=</varname> unit file condition, and thus controls more than just
- <filename>systemd-firstboot.service</filename> behaviour.</para>
+ the relevant settings are not initialized yet. If set to <literal>headless</literal>, the same
+ interactive prompts are suppressed as with <literal>no</literal>, but non-interactive
+ auto-configuration that requires no user input is still performed (for example, selecting the only
+ installed locale, or applying settings provided via credentials). This is useful for unattended
+ installations that should be provisioned as far as possible without ever blocking on a prompt. Not to
+ be confused with <varname>systemd.condition_first_boot=</varname> (see below), which overrides the
+ result of the <varname>ConditionFirstBoot=</varname> unit file condition, and thus controls more than
+ just <filename>systemd-firstboot.service</filename> behaviour.</para>
<xi:include href="version-info.xml" xpointer="v233"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>systemd.firstboot=</varname></term>
- <listitem><para>Takes a boolean argument, defaults to on. If off, <filename>systemd-firstboot.service</filename>
- will not interactively query the user for basic settings at first boot, even if those settings are not
- initialized yet.</para>
+ <listitem><para>Takes a boolean argument or the special value <literal>headless</literal>, defaults to
+ on. If off, <filename>systemd-firstboot.service</filename> will not interactively query the user for
+ basic settings at first boot, even if those settings are not initialized yet. If set to
+ <literal>headless</literal>, interactive prompts are suppressed just like with <literal>no</literal>,
+ but non-interactive auto-configuration that requires no user input is still performed (for example,
+ selecting the sole installed locale, or applying settings passed in via credentials).</para>
<xi:include href="version-info.xml" xpointer="v233"/></listitem>
</varlistentry>
#include "cryptenroll-interactive.h"
#include "cryptenroll-list.h"
#include "cryptsetup-util.h"
+#include "firstboot-util.h"
#include "glyph-util.h"
#include "libfido2-util.h"
#include "log.h"
-#include "proc-cmdline.h"
#include "prompt-util.h"
#include "string-util.h"
#include "strv.h"
assert(c->node);
/* Honour the systemd.firstboot= kernel command line option, just like systemd-firstboot. */
- bool enabled;
- r = proc_cmdline_get_bool("systemd.firstboot", PROC_CMDLINE_TRUE_WHEN_MISSING, &enabled);
+ FirstBootMode mode;
+ _cleanup_free_ char *bad = NULL;
+ r = firstboot_mode_from_cmdline(&mode, &bad);
if (r < 0)
- log_warning_errno(r, "Failed to parse systemd.firstboot= kernel command line option, ignoring: %m");
- else if (!enabled) {
- log_debug("systemd.firstboot=no set, skipping interactive enrollment.");
+ log_warning_errno(r, "Failed to parse systemd.firstboot= kernel command line option, ignoring%s: %m",
+ bad ? strjoina(" (invalid value '", bad, "')") : "");
+ else if (IN_SET(mode, FIRSTBOOT_NO, FIRSTBOOT_HEADLESS)) {
+ log_debug("systemd.firstboot=%s set, skipping interactive enrollment.",
+ firstboot_mode_to_string(mode));
return 0;
}
#include "errno-util.h"
#include "fd-util.h"
#include "fileio.h"
+#include "firstboot-util.h"
#include "format-table.h"
#include "fs-util.h"
#include "glyph-util.h"
#include "password-quality-util.h"
#include "path-util.h"
#include "plymouth-util.h"
-#include "proc-cmdline.h"
#include "prompt-util.h"
#include "runtime-scope.h"
#include "smack-util.h"
static bool arg_prompt_hostname = false;
static bool arg_prompt_root_password = false;
static bool arg_prompt_root_shell = false;
+static bool arg_headless = false;
static bool arg_copy_locale = false;
static bool arg_copy_keymap = false;
static bool arg_copy_timezone = false;
return r != 0 ? locale_is_installed(name) > 0 : locale_is_valid(name);
}
+static bool headless_skips_prompt_for(const char *what) {
+ assert(what);
+
+ if (!arg_headless)
+ return false;
+
+ log_debug("Running headless, not prompting for %s.", what);
+ return true;
+}
+
static int prompt_locale(int rfd, sd_varlink **mute_console_link) {
_cleanup_strv_free_ char **locales = NULL;
bool acquired_from_creds = false;
/* Not setting arg_locale_message here, since it defaults to LANG anyway */
}
} else {
+ if (headless_skips_prompt_for("locale"))
+ return 0;
+
print_welcome(rfd, mute_console_link);
_cleanup_free_ char *prefill = NULL;
return 0;
}
+ if (headless_skips_prompt_for("keymap"))
+ return 0;
+
r = get_keymaps(&kmaps);
if (r == -ENOENT) /* no keymaps installed */
return log_debug_errno(r, "No keymaps are installed.");
return 0;
}
+ if (headless_skips_prompt_for("timezone"))
+ return 0;
+
r = get_timezones(&zones);
if (r < 0)
return log_error_errno(r, "Cannot query timezone list: %m");
return 0;
}
+ if (headless_skips_prompt_for("hostname"))
+ return 0;
+
print_welcome(rfd, mute_console_link);
r = prompt_loop("Please enter the new hostname",
return 0;
}
+ if (headless_skips_prompt_for("root password"))
+ return 0;
+
print_welcome(rfd, mute_console_link);
msg1 = "Please enter the new root password (empty to skip):";
return 0;
}
+ if (headless_skips_prompt_for("root shell"))
+ return 0;
+
print_welcome(rfd, mute_console_link);
return prompt_loop(
* command line option, because we are called to provision the host with basic settings (as
* opposed to some other file system tree/image) */
- bool enabled;
- r = proc_cmdline_get_bool("systemd.firstboot", /* flags= */ 0, &enabled);
+ FirstBootMode mode;
+ _cleanup_free_ char *bad = NULL;
+ r = firstboot_mode_from_cmdline(&mode, &bad);
if (r < 0)
- return log_error_errno(r, "Failed to parse systemd.firstboot= kernel command line argument, ignoring: %m");
- if (r > 0 && !enabled) {
+ return log_error_errno(r, "Failed to parse systemd.firstboot= kernel command line argument%s: %m",
+ bad ? strjoina(" (invalid value '", bad, "')") : "");
+ if (mode == FIRSTBOOT_NO) {
log_debug("Found systemd.firstboot=no kernel command line argument, turning off all prompts.");
arg_prompt_locale = arg_prompt_keymap = arg_prompt_keymap_auto = arg_prompt_timezone = arg_prompt_hostname = arg_prompt_root_password = arg_prompt_root_shell = false;
+ } else if (mode == FIRSTBOOT_HEADLESS) {
+ log_debug("Found systemd.firstboot=headless kernel command line argument, skipping interactive prompts but keeping non-interactive auto-configuration.");
+ arg_headless = true;
}
}
#include "extract-word.h"
#include "fd-util.h"
#include "fileio.h"
+#include "firstboot-util.h"
#include "format-table.h"
#include "format-util.h"
#include "fs-util.h"
#include "pkcs11-util.h"
#include "plymouth-util.h"
#include "polkit-agent.h"
-#include "proc-cmdline.h"
#include "process-util.h"
#include "prompt-util.h"
#include "recurse-dir.h"
/* Let's honour the systemd.firstboot kernel command line option, just like the systemd-firstboot
* tool. */
- bool enabled;
- r = proc_cmdline_get_bool("systemd.firstboot", /* flags= */ 0, &enabled);
+ FirstBootMode mode;
+ _cleanup_free_ char *bad = NULL;
+ r = firstboot_mode_from_cmdline(&mode, &bad);
if (r < 0)
- return log_error_errno(r, "Failed to parse systemd.firstboot= kernel command line argument, ignoring: %m");
- if (r > 0 && !enabled) {
- log_debug("Found systemd.firstboot=no kernel command line argument, turning off all prompts.");
+ return log_error_errno(r, "Failed to parse systemd.firstboot= kernel command line argument%s: %m",
+ bad ? strjoina(" (invalid value '", bad, "')") : "");
+ if (IN_SET(mode, FIRSTBOOT_NO, FIRSTBOOT_HEADLESS)) {
+ log_debug("Found systemd.firstboot=%s kernel command line argument, turning off all prompts.",
+ firstboot_mode_to_string(mode));
arg_prompt_new_user = false;
}
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "firstboot-util.h"
+#include "proc-cmdline.h"
+#include "string-table.h"
+
+static const char* const firstboot_mode_table[_FIRSTBOOT_MODE_MAX] = {
+ [FIRSTBOOT_NO] = "no",
+ [FIRSTBOOT_INTERACTIVE] = "interactive",
+ [FIRSTBOOT_HEADLESS] = "headless",
+};
+
+assert_cc(FIRSTBOOT_NO == 0);
+
+DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(firstboot_mode, FirstBootMode, FIRSTBOOT_INTERACTIVE);
+
+int firstboot_mode_from_cmdline(FirstBootMode *ret, char **reterr_value) {
+ _cleanup_free_ char *value = NULL;
+ int r;
+
+ assert(ret);
+
+ r = proc_cmdline_get_key("systemd.firstboot", PROC_CMDLINE_VALUE_OPTIONAL, &value);
+ if (r < 0)
+ return r;
+ if (r == 0) { /* not specified at all */
+ *ret = FIRSTBOOT_INTERACTIVE;
+ return 0;
+ }
+ if (!value) { /* key without parameter, i.e. bare "systemd.firstboot" */
+ *ret = FIRSTBOOT_INTERACTIVE;
+ return 1;
+ }
+
+ FirstBootMode m = firstboot_mode_from_string(value);
+ if (m < 0) {
+ if (reterr_value)
+ *reterr_value = TAKE_PTR(value);
+ return m;
+ }
+
+ *ret = m;
+ return 1;
+}
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "shared-forward.h"
+
+/* Parsed value of the systemd.firstboot= kernel command line option, honoured by systemd-firstboot,
+ * homectl's firstboot logic and systemd-cryptenroll. */
+typedef enum FirstBootMode {
+ FIRSTBOOT_NO, /* "no": don't prompt, don't auto-configure */
+ FIRSTBOOT_INTERACTIVE, /* "interactive" or unset: prompt as needed */
+ FIRSTBOOT_HEADLESS, /* "headless": auto-configure, but never prompt */
+ _FIRSTBOOT_MODE_MAX,
+ _FIRSTBOOT_MODE_INVALID = -EINVAL,
+} FirstBootMode;
+
+DECLARE_STRING_TABLE_LOOKUP(firstboot_mode, FirstBootMode);
+
+int firstboot_mode_from_cmdline(FirstBootMode *ret, char **reterr_value);
'fido2-util.c',
'find-esp.c',
'firewall-util.c',
+ 'firstboot-util.c',
'fork-notify.c',
'format-table.c',
'fsprg-openssl.c',
'test-fiemap.c',
'test-fileio.c',
'test-firewall-util.c',
+ 'test-firstboot-util.c',
'test-format-table.c',
'test-format-util.c',
'test-fs-util.c',
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "firstboot-util.h"
+#include "tests.h"
+
+TEST(firstboot_mode_from_string) {
+ assert_se(firstboot_mode_from_string("yes") == FIRSTBOOT_INTERACTIVE);
+ assert_se(firstboot_mode_from_string("1") == FIRSTBOOT_INTERACTIVE);
+ assert_se(firstboot_mode_from_string("on") == FIRSTBOOT_INTERACTIVE);
+ assert_se(firstboot_mode_from_string("true") == FIRSTBOOT_INTERACTIVE);
+ assert_se(firstboot_mode_from_string("interactive") == FIRSTBOOT_INTERACTIVE);
+
+ assert_se(firstboot_mode_from_string("no") == FIRSTBOOT_NO);
+ assert_se(firstboot_mode_from_string("0") == FIRSTBOOT_NO);
+ assert_se(firstboot_mode_from_string("off") == FIRSTBOOT_NO);
+ assert_se(firstboot_mode_from_string("false") == FIRSTBOOT_NO);
+
+ assert_se(firstboot_mode_from_string("headless") == FIRSTBOOT_HEADLESS);
+
+ assert_se(firstboot_mode_from_string("") == _FIRSTBOOT_MODE_INVALID);
+ assert_se(firstboot_mode_from_string(NULL) == _FIRSTBOOT_MODE_INVALID);
+ assert_se(firstboot_mode_from_string("Headless") == _FIRSTBOOT_MODE_INVALID);
+ assert_se(firstboot_mode_from_string("maybe") == _FIRSTBOOT_MODE_INVALID);
+}
+
+DEFINE_TEST_MAIN(LOG_INFO);
#include "device-private.h"
#include "discover-image.h"
#include "execute.h"
+#include "firstboot-util.h"
#include "gpt.h"
#include "import-util.h"
#include "install.h"
test_table(ExecOutput, exec_output, EXEC_OUTPUT);
test_table(ExecPreserveMode, exec_preserve_mode, EXEC_PRESERVE_MODE);
test_table(ExecUtmpMode, exec_utmp_mode, EXEC_UTMP_MODE);
+ test_table(FirstBootMode, firstboot_mode, FIRSTBOOT_MODE);
test_table(ImageType, image_type, IMAGE_TYPE);
test_table(ImportVerify, import_verify, IMPORT_VERIFY);
test_table(JobMode, job_mode, JOB_MODE);