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:
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user