mirror of
https://github.com/systemd/systemd.git
synced 2026-08-10 17:14:31 +00:00
firstboot: add new systemd.firstboot=headless mode
This adds a new systemd.firstboot=headless mode. It differs from the existing systemd.firstboot=no mode in that it still performs the non-interactive auto-configuration that requires no user input (such as selecting the only installed locale, or applying settings provided via credentials), and only skips the prompts that would otherwise block waiting for user input. In contrast, =no disables that auto-configuration along with the prompts. The option is also honoured by homectls firstboot logic and systemd-cryptenroll, where headless behaves the same as no (because the is no auto-configuration).
This commit is contained in:
committed by
Luca Boccassi
parent
03b498d8a8
commit
896fd702ba
11
NEWS
11
NEWS
@@ -16,6 +16,17 @@ CHANGES WITH 262:
|
||||
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:
|
||||
|
||||
@@ -645,15 +645,20 @@
|
||||
<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>
|
||||
|
||||
@@ -521,9 +521,12 @@
|
||||
<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>
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
#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"
|
||||
@@ -176,12 +176,15 @@ int cryptenroll_run_interactive(
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#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"
|
||||
@@ -47,7 +48,6 @@
|
||||
#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"
|
||||
@@ -79,6 +79,7 @@ static bool arg_prompt_timezone = false;
|
||||
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;
|
||||
@@ -244,6 +245,16 @@ static int locale_is_ok(const char *name, void *userdata) {
|
||||
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;
|
||||
@@ -296,6 +307,9 @@ static int prompt_locale(int rfd, sd_varlink **mute_console_link) {
|
||||
/* 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;
|
||||
@@ -456,6 +470,9 @@ static int prompt_keymap(int rfd, sd_varlink **mute_console_link) {
|
||||
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.");
|
||||
@@ -578,6 +595,9 @@ static int prompt_timezone(int rfd, sd_varlink **mute_console_link) {
|
||||
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");
|
||||
@@ -691,6 +711,9 @@ static int prompt_hostname(int rfd, sd_varlink **mute_console_link) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (headless_skips_prompt_for("hostname"))
|
||||
return 0;
|
||||
|
||||
print_welcome(rfd, mute_console_link);
|
||||
|
||||
r = prompt_loop("Please enter the new hostname",
|
||||
@@ -879,6 +902,9 @@ static int prompt_root_password(int rfd, sd_varlink **mute_console_link) {
|
||||
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):";
|
||||
@@ -980,6 +1006,9 @@ static int prompt_root_shell(int rfd, sd_varlink **mute_console_link) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (headless_skips_prompt_for("root shell"))
|
||||
return 0;
|
||||
|
||||
print_welcome(rfd, mute_console_link);
|
||||
|
||||
return prompt_loop(
|
||||
@@ -1688,13 +1717,18 @@ static int run(int argc, char *argv[]) {
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#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"
|
||||
@@ -52,7 +53,6 @@
|
||||
#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"
|
||||
@@ -3120,12 +3120,15 @@ static int verb_firstboot(int argc, char *argv[], uintptr_t _data, void *userdat
|
||||
/* 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;
|
||||
}
|
||||
|
||||
|
||||
45
src/shared/firstboot-util.c
Normal file
45
src/shared/firstboot-util.c
Normal file
@@ -0,0 +1,45 @@
|
||||
/* 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;
|
||||
}
|
||||
18
src/shared/firstboot-util.h
Normal file
18
src/shared/firstboot-util.h
Normal file
@@ -0,0 +1,18 @@
|
||||
/* 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);
|
||||
@@ -85,6 +85,7 @@ shared_sources = files(
|
||||
'fido2-util.c',
|
||||
'find-esp.c',
|
||||
'firewall-util.c',
|
||||
'firstboot-util.c',
|
||||
'fork-notify.c',
|
||||
'format-table.c',
|
||||
'fsprg-openssl.c',
|
||||
|
||||
@@ -108,6 +108,7 @@ simple_tests += files(
|
||||
'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',
|
||||
|
||||
26
src/test/test-firstboot-util.c
Normal file
26
src/test/test-firstboot-util.c
Normal file
@@ -0,0 +1,26 @@
|
||||
/* 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);
|
||||
@@ -12,6 +12,7 @@
|
||||
#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"
|
||||
@@ -62,6 +63,7 @@ int main(int argc, char **argv) {
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user