1
0
mirror of https://github.com/systemd/systemd synced 2025-10-06 00:13:24 +02:00

homectl firstboot tweaks (#39137)

This adds what #39101 and #39070 did for the regular firstboot wizard
but for the homectl firstboot part: i.e. port to the generic prompt
loop, show the "chrome" bars, and mute the console.

And then it also makes querying for aux groups and shells optional,
because quite frankly, i am not sure what to answer there.
This commit is contained in:
Lennart Poettering
2025-09-29 10:56:27 +02:00
committed by GitHub
7 changed files with 321 additions and 114 deletions

View File

@@ -205,6 +205,56 @@
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--prompt-new-user</option></term>
<listitem><para>If used in conjunction with <command>firstboot</command> and no regular user account
exists on the system so far the tool will interactively query for user information and create an
account.</para>
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--prompt-shell=</option></term>
<listitem><para>Takes a boolean argument. Controls whether to interactively query for a login shell,
if <command>firstboot --prompt-new-user</command> is used. Defaults to true.</para>
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--prompt-groups=</option></term>
<listitem><para>Takes a boolean argument. Controls whether to interactively query for auxiliary
groups to add the new user to, if <command>firstboot --prompt-new-user</command> is used. Defaults to
true.</para>
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--chrome=</option></term>
<listitem><para>Takes a boolean argument. By default the interactive user creation via
<command>firstboot --prompt-new-user</command> screen will show reverse color "chrome" bars at the
top and and the bottom of the terminal screen, which may be disabled by setting this option to
false.</para>
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--mute-console=</option></term>
<listitem><para>Takes a boolean argument. If true kernel log output and service manager status output
to the system console is temporarily disabled while <command>firstboot --prompt-new-user</command> is
running, so that its own output is not interrupted. Defaults to false.</para>
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--match=</option></term>
<term><option>-A</option></term>
@@ -1424,9 +1474,10 @@ $ ssh lennart@targetsystem</programlisting>
<listitem><para>This command is supposed to be invoked during the initial boot of the system. It
checks whether any regular home area exists so far, and if not queries the user interactively on the
console for user name and password and creates one. Alternatively, if one or more service credentials
whose name starts with <literal>home.create.</literal> are passed to the command (containing a user
record in JSON format) these users are automatically created at boot.</para>
console for user name and password and creates one (only if <option>--prompt-new-user</option> is
specified). Alternatively, if one or more service credentials whose name starts with
<literal>home.create.</literal> are passed to the command (containing a user record in JSON format)
these users are automatically created at boot.</para>
<para>This command is invoked by the <filename>systemd-homed-firstboot.service</filename> service
unit.</para>

View File

@@ -271,6 +271,17 @@
</listitem>
</varlistentry>
<varlistentry>
<term><option>--prompt-keymap-auto</option></term>
<listitem><para>If invoked from a virtual terminal TTY equivalent to
<option>--prompt-keymap</option>, otherwise has no effect. Or in other words, only prompts
interactively for a keymap if a local keyboard is used for interactivity that requires it.</para>
<xi:include href="version-info.xml" xpointer="v259"/>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--copy-locale</option></term>
<term><option>--copy-keymap</option></term>

View File

@@ -71,6 +71,7 @@ static char *arg_root_shell = NULL;
static char *arg_kernel_cmdline = NULL;
static bool arg_prompt_locale = false;
static bool arg_prompt_keymap = false;
static bool arg_prompt_keymap_auto = false;
static bool arg_prompt_timezone = false;
static bool arg_prompt_hostname = false;
static bool arg_prompt_root_password = false;
@@ -138,18 +139,16 @@ static void print_welcome(int rfd, sd_varlink **mute_console_link) {
ac = isempty(ansi_color) ? "0" : ansi_color;
if (colors_enabled())
printf(ANSI_HIGHLIGHT "Welcome to your new installation of " ANSI_NORMAL "\x1B[%sm%s" ANSI_HIGHLIGHT "!" ANSI_NORMAL "\n", ac, pn);
printf(ANSI_HIGHLIGHT "Welcome to " ANSI_NORMAL "\x1B[%sm%s" ANSI_HIGHLIGHT "!" ANSI_NORMAL "\n", ac, pn);
else
printf("Welcome to your new installation of %s!\n", pn);
printf("Welcome to %s!\n", pn);
putchar('\n');
if (emoji_enabled()) {
fputs(glyph(GLYPH_SPARKLES), stdout);
putchar(' ');
}
printf("Please configure your new system!\n");
any_key_to_proceed();
printf("Please configure the system!\n\n");
done = true;
}
@@ -417,7 +416,21 @@ static int prompt_keymap(int rfd, sd_varlink **mute_console_link) {
return 0;
}
if (!arg_prompt_keymap) {
bool b;
if (arg_prompt_keymap_auto) {
_cleanup_free_ char *ttyname = NULL;
r = getttyname_harder(STDOUT_FILENO, &ttyname);
if (r < 0) {
log_debug_errno(r, "Cannot determine TTY we are connected, ignoring: %m");
b = false; /* if we can't resolve this, it's probably not a VT */
} else {
b = tty_is_vc_resolve(ttyname);
log_debug("Detected connection to local console: %s", yes_no(b));
}
} else
b = arg_prompt_keymap;
if (!b) {
log_debug("Prompting for keymap was not requested.");
return 0;
}
@@ -1236,6 +1249,8 @@ static int help(void) {
" Set kernel command line\n"
" --prompt-locale Prompt the user for locale settings\n"
" --prompt-keymap Prompt the user for keymap settings\n"
" --prompt-keymap-auto Prompt the user for keymap settings if invoked\n"
" on local console\n"
" --prompt-timezone Prompt the user for timezone\n"
" --prompt-hostname Prompt the user for hostname\n"
" --prompt-root-password Prompt the user for root password\n"
@@ -1286,6 +1301,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_PROMPT,
ARG_PROMPT_LOCALE,
ARG_PROMPT_KEYMAP,
ARG_PROMPT_KEYMAP_AUTO,
ARG_PROMPT_TIMEZONE,
ARG_PROMPT_HOSTNAME,
ARG_PROMPT_ROOT_PASSWORD,
@@ -1325,6 +1341,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "prompt", no_argument, NULL, ARG_PROMPT },
{ "prompt-locale", no_argument, NULL, ARG_PROMPT_LOCALE },
{ "prompt-keymap", no_argument, NULL, ARG_PROMPT_KEYMAP },
{ "prompt-keymap-auto", no_argument, NULL, ARG_PROMPT_KEYMAP_AUTO },
{ "prompt-timezone", no_argument, NULL, ARG_PROMPT_TIMEZONE },
{ "prompt-hostname", no_argument, NULL, ARG_PROMPT_HOSTNAME },
{ "prompt-root-password", no_argument, NULL, ARG_PROMPT_ROOT_PASSWORD },
@@ -1482,6 +1499,7 @@ static int parse_argv(int argc, char *argv[]) {
case ARG_PROMPT:
arg_prompt_locale = arg_prompt_keymap = arg_prompt_timezone = arg_prompt_hostname =
arg_prompt_root_password = arg_prompt_root_shell = true;
arg_prompt_keymap_auto = false;
break;
case ARG_PROMPT_LOCALE:
@@ -1490,6 +1508,11 @@ static int parse_argv(int argc, char *argv[]) {
case ARG_PROMPT_KEYMAP:
arg_prompt_keymap = true;
arg_prompt_keymap_auto = false;
break;
case ARG_PROMPT_KEYMAP_AUTO:
arg_prompt_keymap_auto = true;
break;
case ARG_PROMPT_TIMEZONE:
@@ -1670,7 +1693,7 @@ static int run(int argc, char *argv[]) {
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.");
arg_prompt_locale = arg_prompt_keymap = arg_prompt_timezone = arg_prompt_hostname = arg_prompt_root_password = arg_prompt_root_shell = false;
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;
}
}

View File

@@ -4,6 +4,7 @@
#include <unistd.h>
#include "sd-bus.h"
#include "sd-varlink.h"
#include "ask-password-api.h"
#include "bitfield.h"
@@ -15,6 +16,7 @@
#include "capability-list.h"
#include "capability-util.h"
#include "cgroup-util.h"
#include "chase.h"
#include "creds-util.h"
#include "dirent-util.h"
#include "dns-domain.h"
@@ -49,6 +51,7 @@
#include "pretty-print.h"
#include "proc-cmdline.h"
#include "process-util.h"
#include "prompt-util.h"
#include "recurse-dir.h"
#include "rlimit-util.h"
#include "runtime-scope.h"
@@ -101,13 +104,17 @@ static enum {
} arg_export_format = EXPORT_FORMAT_FULL;
static uint64_t arg_capability_bounding_set = UINT64_MAX;
static uint64_t arg_capability_ambient_set = UINT64_MAX;
static bool arg_prompt_new_user = false;
static char *arg_blob_dir = NULL;
static bool arg_blob_clear = false;
static Hashmap *arg_blob_files = NULL;
static char *arg_key_name = NULL;
static bool arg_dry_run = false;
static bool arg_seize = true;
static bool arg_prompt_new_user = false;
static bool arg_prompt_shell = true;
static bool arg_prompt_groups = true;
static bool arg_chrome = true;
static bool arg_mute_console = false;
STATIC_DESTRUCTOR_REGISTER(arg_identity_extra, sd_json_variant_unrefp);
STATIC_DESTRUCTOR_REGISTER(arg_identity_extra_this_machine, sd_json_variant_unrefp);
@@ -2680,77 +2687,21 @@ static int group_completion_callback(const char *key, char ***ret_list, void *us
return 0;
}
static int create_interactively(void) {
_cleanup_free_ char *username = NULL;
static int prompt_groups(const char *username, char ***ret_groups) {
int r;
if (!arg_prompt_new_user) {
log_debug("Prompting for user creation was not requested.");
assert(username);
assert(ret_groups);
if (!arg_prompt_groups) {
*ret_groups = NULL;
return 0;
}
putchar('\n');
if (emoji_enabled()) {
fputs(glyph(GLYPH_HOME), stdout);
putchar(' ');
}
printf("Please create your user account!\n");
if (!any_key_to_proceed()) {
log_notice("Skipping.");
return 0;
}
(void) terminal_reset_defensive_locked(STDOUT_FILENO, /* flags= */ 0);
for (;;) {
username = mfree(username);
r = ask_string(&username,
"%s Please enter user name to create (empty to skip): ",
glyph(GLYPH_TRIANGULAR_BULLET));
if (r < 0)
return log_error_errno(r, "Failed to query user for username: %m");
if (isempty(username)) {
log_info("No data entered, skipping.");
return 0;
}
if (!valid_user_group_name(username, /* flags= */ 0)) {
log_notice("Specified user name is not a valid UNIX user name, try again: %s", username);
continue;
}
r = userdb_by_name(username, /* match= */ NULL, USERDB_SUPPRESS_SHADOW, /* ret= */ NULL);
if (r == -ESRCH)
break;
if (r < 0)
return log_error_errno(r, "Failed to check if specified user '%s' already exists: %m", username);
log_notice("Specified user '%s' exists already, try again.", username);
}
r = sd_json_variant_set_field_string(&arg_identity_extra, "userName", username);
if (r < 0)
return log_error_errno(r, "Failed to set userName field: %m");
/* Let's not insist on a strong password in the firstboot interactive interface. Insisting on this is
* really annoying, as the user cannot just invoke the tool again with "--enforce-password-policy=no"
* because after all the tool is called from the boot process, and not from an interactive
* shell. Moreover, when setting up an initial system we can assume the user owns it, and hence we
* don't need to hard enforce some policy on password strength some organization or OS vendor
* requires. Note that this just disables the *strict* enforcement of the password policy. Even with
* this disabled we'll still tell the user in the UI that the password is too weak and suggest better
* ones, even if we then accept the weak ones if the user insists, by repeating it. */
r = sd_json_variant_set_field_boolean(&arg_identity_extra, "enforcePasswordPolicy", false);
if (r < 0)
return log_error_errno(r, "Failed to set enforcePasswordPolicy field: %m");
_cleanup_strv_free_ char **available = NULL, **groups = NULL;
for (;;) {
_cleanup_free_ char *s = NULL;
strv_sort_uniq(groups);
if (!strv_isempty(groups)) {
@@ -2761,10 +2712,11 @@ static int create_interactively(void) {
log_info("Currently selected groups: %s", j);
}
_cleanup_free_ char *s = NULL;
r = ask_string_full(&s,
group_completion_callback, &available,
"%s Please enter an auxiliary group for user %s (empty to continue, \"list\" to list available groups): ",
glyph(GLYPH_TRIANGULAR_BULLET), username);
glyph(GLYPH_LABEL), username);
if (r < 0)
return log_error_errno(r, "Failed to query user for auxiliary group: %m");
@@ -2834,6 +2786,148 @@ static int create_interactively(void) {
return log_oom();
}
*ret_groups = TAKE_PTR(groups);
return 0;
}
static int shell_is_ok(const char *path, void *userdata) {
int r;
assert(path);
if (!valid_shell(path)) {
log_error("String '%s' is not a valid path to a shell, refusing.", path);
return false;
}
r = chase_and_access(path, /* root= */ NULL, CHASE_MUST_BE_REGULAR, X_OK, /* ret_path= */ NULL) >= 0;
if (r == -ENOENT) {
log_error_errno(r, "Shell '%s' does not exist, try again.", path);
return false;
}
if (ERRNO_IS_NEG_PRIVILEGE(r)) {
log_error_errno(r, "File '%s' is not executable, try again.", path);
return false;
}
if (r < 0)
return log_error_errno(r, "Failed to check if shell '%s' exists and is executable: %m", path);
return true;
}
static int prompt_shell(const char *username, char **ret_shell) {
assert(username);
assert(ret_shell);
if (!arg_prompt_shell) {
*ret_shell = NULL;
return 0;
}
putchar('\n');
_cleanup_free_ char *q = strjoin("Please enter the shell to use for user ", username);
if (!q)
return log_oom();
return prompt_loop(
q,
GLYPH_SHELL,
/* menu= */ NULL,
/* accepted= */ NULL,
/* ellipsize_percentage= */ 0,
/* n_columns= */ 3,
/* column_width= */ 20,
shell_is_ok,
/* refresh= */ NULL,
/* userdata= */ NULL,
PROMPT_MAY_SKIP|PROMPT_SILENT_VALIDATE,
ret_shell);
}
static int username_is_ok(const char *name, void *userdata) {
int r;
assert(name);
if (!valid_user_group_name(name, /* flags= */ 0)) {
log_notice("Specified user name is not a valid UNIX user name, try again: %s", name);
return false;
}
r = userdb_by_name(name, /* match= */ NULL, USERDB_SUPPRESS_SHADOW, /* ret= */ NULL);
if (r == -ESRCH)
return true;
if (r < 0)
return log_error_errno(r, "Failed to check if specified user '%s' already exists: %m", name);
log_notice("Specified user '%s' exists already, try again.", name);
return false;
}
static int create_interactively(void) {
_cleanup_free_ char *username = NULL;
int r;
if (!arg_prompt_new_user) {
log_debug("Prompting for user creation was not requested.");
return 0;
}
_cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *mute_console_link = NULL;
(void) mute_console(&mute_console_link);
(void) terminal_reset_defensive_locked(STDOUT_FILENO, /* flags= */ 0);
if (arg_chrome)
chrome_show("Create a User Account", /* bottom= */ NULL);
DEFER_VOID_CALL(chrome_hide);
if (emoji_enabled()) {
fputs(glyph(GLYPH_HOME), stdout);
putchar(' ');
}
printf("Please create your user account!\n\n");
r = prompt_loop("Please enter user name to create",
GLYPH_IDCARD,
/* menu= */ NULL,
/* accepted= */ NULL,
/* ellipsize_percentage= */ 60,
/* n_columns= */ 3,
/* column_width= */ 20,
username_is_ok,
/* refresh= */ NULL,
/* userdata= */ NULL,
PROMPT_MAY_SKIP|PROMPT_SILENT_VALIDATE,
&username);
if (r < 0)
return r;
if (isempty(username))
return 0;
r = sd_json_variant_set_field_string(&arg_identity_extra, "userName", username);
if (r < 0)
return log_error_errno(r, "Failed to set userName field: %m");
/* Let's not insist on a strong password in the firstboot interactive interface. Insisting on this is
* really annoying, as the user cannot just invoke the tool again with "--enforce-password-policy=no"
* because after all the tool is called from the boot process, and not from an interactive
* shell. Moreover, when setting up an initial system we can assume the user owns it, and hence we
* don't need to hard enforce some policy on password strength some organization or OS vendor
* requires. Note that this just disables the *strict* enforcement of the password policy. Even with
* this disabled we'll still tell the user in the UI that the password is too weak and suggest better
* ones, even if we then accept the weak ones if the user insists, by repeating it. */
r = sd_json_variant_set_field_boolean(&arg_identity_extra, "enforcePasswordPolicy", false);
if (r < 0)
return log_error_errno(r, "Failed to set enforcePasswordPolicy field: %m");
_cleanup_strv_free_ char **groups = NULL;
r = prompt_groups(username, &groups);
if (r < 0)
return r;
if (!strv_isempty(groups)) {
strv_sort_uniq(groups);
@@ -2843,35 +2937,9 @@ static int create_interactively(void) {
}
_cleanup_free_ char *shell = NULL;
for (;;) {
shell = mfree(shell);
r = ask_string(&shell,
"%s Please enter the shell to use for user %s (empty for default): ",
glyph(GLYPH_TRIANGULAR_BULLET), username);
if (r < 0)
return log_error_errno(r, "Failed to query user for username: %m");
if (isempty(shell)) {
log_info("No data entered, leaving at default.");
break;
}
if (!valid_shell(shell)) {
log_notice("Specified shell is not a valid UNIX shell path, try again: %s", shell);
continue;
}
r = RET_NERRNO(access(shell, X_OK));
if (r >= 0)
break;
if (r != -ENOENT)
return log_error_errno(r, "Failed to check if shell %s exists: %m", shell);
log_notice("Specified shell '%s' is not installed, try another one.", shell);
}
r = prompt_shell(username, &shell);
if (r < 0)
return r;
if (!isempty(shell)) {
log_info("Selected %s as the shell for user %s", shell, username);
@@ -2881,7 +2949,14 @@ static int create_interactively(void) {
return log_error_errno(r, "Failed to set shell field: %m");
}
return create_home_common(/* input= */ NULL, /* show_enforce_password_policy_hint= */ false);
putchar('\n');
r = create_home_common(/* input= */ NULL, /* show_enforce_password_policy_hint= */ false);
if (r < 0)
return r;
log_info("Successfully created account '%s'.", username);
return 0;
}
static int add_signing_keys_from_credentials(void);
@@ -3021,11 +3096,18 @@ static int help(int argc, char *argv[], void *userdata) {
" -E When specified once equals -j --export-format=\n"
" stripped, when specified twice equals\n"
" -j --export-format=minimal\n"
" --prompt-new-user firstboot: Query user interactively for user\n"
" to create\n"
" --key-name=NAME Key name when adding a signing key\n"
" --seize=no Do not strip existing signatures of user record\n"
" when creating\n"
" --prompt-new-user firstboot: Query user interactively for user\n"
" to create\n"
" --prompt-groups=no In first-boot mode, don't prompt for auxiliary\n"
" group memberships\n"
" --prompt-shell=no In first-boot mode, don't prompt for shells\n"
" --chrome=no In first-boot mode, don't show colour bar at top\n"
" and bottom of terminal\n"
" --mute-console=yes In first-boot mode, tell kernel/PID 1 to not\n"
" write to the console while running\n"
"\n%4$sGeneral User Record Properties:%5$s\n"
" -c --real-name=REALNAME Real name for user\n"
" --realm=REALM Realm to create user in\n"
@@ -3262,6 +3344,10 @@ static int parse_argv(int argc, char *argv[]) {
ARG_KEY_NAME,
ARG_SEIZE,
ARG_MATCH,
ARG_PROMPT_SHELL,
ARG_PROMPT_GROUPS,
ARG_CHROME,
ARG_MUTE_CONSOLE,
};
static const struct option options[] = {
@@ -3368,6 +3454,10 @@ static int parse_argv(int argc, char *argv[]) {
{ "key-name", required_argument, NULL, ARG_KEY_NAME },
{ "seize", required_argument, NULL, ARG_SEIZE },
{ "match", required_argument, NULL, ARG_MATCH },
{ "prompt-shell", required_argument, NULL, ARG_PROMPT_SHELL },
{ "prompt-groups", required_argument, NULL, ARG_PROMPT_GROUPS },
{ "chrome", required_argument, NULL, ARG_CHROME },
{ "mute-console", required_argument, NULL, ARG_MUTE_CONSOLE },
{}
};
@@ -4927,6 +5017,34 @@ static int parse_argv(int argc, char *argv[]) {
match_identity = &arg_identity_extra_other_machines;
break;
case ARG_PROMPT_SHELL:
r = parse_boolean_argument("--prompt-shell=", optarg, &arg_prompt_shell);
if (r < 0)
return r;
break;
case ARG_PROMPT_GROUPS:
r = parse_boolean_argument("--prompt-groups=", optarg, &arg_prompt_groups);
if (r < 0)
return r;
break;
case ARG_CHROME:
r = parse_boolean_argument("--chrome=", optarg, &arg_chrome);
if (r < 0)
return r;
break;
case ARG_MUTE_CONSOLE:
r = parse_boolean_argument("--mute-console=", optarg, &arg_mute_console);
if (r < 0)
return r;
break;
case '?':
return -EINVAL;

View File

@@ -211,7 +211,7 @@ set +o pipefail
# We can do only limited testing here, since it's all an interactive stuff, so
# --prompt is skipped on purpose and only limited --prompt-root-password
# testing can be done.
echo -ne "\nfoo\nbar\n" | systemd-firstboot --root="$ROOT" --prompt-locale
echo -ne "foo\nbar\n" | systemd-firstboot --root="$ROOT" --prompt-locale
grep -q "LANG=foo" "$ROOT$LOCALE_PATH"
grep -q "LC_MESSAGES=bar" "$ROOT$LOCALE_PATH"
# systemd-firstboot in prompt-keymap mode requires keymaps to be installed so
@@ -219,22 +219,26 @@ grep -q "LC_MESSAGES=bar" "$ROOT$LOCALE_PATH"
# compatible keymaps (from the kbd package), skip this test if the keymaps are
# missing.
if [ -d "/usr/share/keymaps/" ] || [ -d "/usr/share/kbd/keymaps/" ] || [ -d "/usr/lib/kbd/keymaps/" ] ; then
echo -ne "\nfoo\n" | systemd-firstboot --root="$ROOT" --prompt-keymap
echo -ne "foo\n" | systemd-firstboot --root="$ROOT" --prompt-keymap
grep -q "KEYMAP=foo" "$ROOT/etc/vconsole.conf"
rm "$ROOT/etc/vconsole.conf"
# this should be a NOP, given that stdout is connected to /dev/null, and hence not a VT
systemd-firstboot --root="$ROOT" --prompt-keymap-auto > /dev/null
fi
echo -ne "\nEurope/Berlin\n" | systemd-firstboot --root="$ROOT" --prompt-timezone
echo -ne "Europe/Berlin\n" | systemd-firstboot --root="$ROOT" --prompt-timezone
readlink "$ROOT/etc/localtime" | grep -q "Europe/Berlin$"
echo -ne "\nfoobar\n" | systemd-firstboot --root="$ROOT" --prompt-hostname
echo -ne "foobar\n" | systemd-firstboot --root="$ROOT" --prompt-hostname
grep -q "foobar" "$ROOT/etc/hostname"
# With no root password provided, a locked account should be created.
systemd-firstboot --root="$ROOT" --prompt-root-password </dev/null
grep -q "^root:x:0:0:" "$ROOT/etc/passwd"
grep -q "^root:!\*:" "$ROOT/etc/shadow"
rm -fv "$ROOT/etc/passwd" "$ROOT/etc/shadow"
echo -ne "\n/bin/fooshell\n" | systemd-firstboot --root="$ROOT" --prompt-root-shell
echo -ne "/bin/fooshell\n" | systemd-firstboot --root="$ROOT" --prompt-root-shell
grep -q "^root:.*:0:0:.*:/bin/fooshell$" "$ROOT/etc/passwd"
# Existing files should not get overwritten
echo -ne "\n/bin/barshell\n" | systemd-firstboot --root="$ROOT" --prompt-root-shell
echo -ne "/bin/barshell\n" | systemd-firstboot --root="$ROOT" --prompt-root-shell
grep -q "^root:.*:0:0:.*:/bin/fooshell$" "$ROOT/etc/passwd"
# Now without the welcome screen but with force
echo -ne "/bin/barshell\n" | systemd-firstboot --root="$ROOT" --force --prompt-root-shell --welcome=no

View File

@@ -32,7 +32,7 @@ Before=shutdown.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=systemd-firstboot --prompt-locale --prompt-keymap --prompt-timezone --prompt-root-password --mute-console=yes
ExecStart=systemd-firstboot --prompt-locale --prompt-keymap-auto --prompt-timezone --prompt-root-password --mute-console=yes
StandardOutput=tty
StandardInput=tty
StandardError=tty

View File

@@ -17,7 +17,7 @@ Before=systemd-user-sessions.service first-boot-complete.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=homectl firstboot --prompt-new-user
ExecStart=homectl firstboot --prompt-new-user --prompt-shell=no --prompt-groups=no --mute-console=yes
StandardOutput=tty
StandardInput=tty
StandardError=tty