mirror of
https://github.com/systemd/systemd.git
synced 2024-12-22 17:35:35 +03:00
Merge pull request #30150 from poettering/homectl-interactive
add "homectl firstboot" verb, that runs at first boot and can create a user, interactively or from creds
This commit is contained in:
commit
dadc06bc6c
9
TODO
9
TODO
@ -934,10 +934,6 @@ Features:
|
||||
file system paths to enable on start.
|
||||
• make systemd-fstab-generator look for a system credential encoding root= or
|
||||
usr=
|
||||
• systemd-homed: when initializing, look for a credential
|
||||
systemd.homed.register or so with JSON user records to automatically
|
||||
register if not registered yet. Use case: deploy a system, and add an
|
||||
account one can directly log into.
|
||||
• in gpt-auto-generator: check partition uuids against such uuids supplied via
|
||||
sd-stub credentials. That way, we can support parallel OS installations with
|
||||
pre-built kernels.
|
||||
@ -2262,11 +2258,6 @@ Features:
|
||||
- support new FS_IOC_ADD_ENCRYPTION_KEY ioctl for setting up fscrypt
|
||||
- maybe pre-create ~/.cache as subvol so that it can have separate quota
|
||||
easily?
|
||||
- add a switch to homectl (maybe called --first-boot) where it will check if
|
||||
any non-system users exist, and if not prompts interactively for basic user
|
||||
info, mimicking systemd-firstboot. Then, place this in a service that runs
|
||||
after systemd-homed, but before gdm and friends, as a simple, barebones
|
||||
fallback logic to get a regular user created on uninitialized systems.
|
||||
- store PKCS#11 + FIDO2 token info in LUKS2 header, compatible with
|
||||
systemd-cryptsetup, so that it can unlock homed volumes
|
||||
- maybe make all *.home files owned by `systemd-home` user or so, so that we
|
||||
|
@ -18,6 +18,7 @@
|
||||
|
||||
<refnamediv>
|
||||
<refname>homectl</refname>
|
||||
<refname>systemd-homed-firstboot.service</refname>
|
||||
<refpurpose>Create, remove, change or inspect home directories</refpurpose>
|
||||
</refnamediv>
|
||||
|
||||
@ -1138,6 +1139,59 @@
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v250"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><command>firstboot</command></term>
|
||||
|
||||
<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>
|
||||
|
||||
<para>This command is invoked by the <filename>systemd-homed-firstboot.service</filename> service
|
||||
unit.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Credentials</title>
|
||||
|
||||
<para>When invoked with the <command>firstboot</command> command, <command>homectl</command> supports the
|
||||
service credentials logic as implemented by
|
||||
<varname>ImportCredential=</varname>/<varname>LoadCredential=</varname>/<varname>SetCredential=</varname>
|
||||
(see <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
|
||||
details). The following credentials are used when passed in:</para>
|
||||
|
||||
<variablelist class='system-credentials'>
|
||||
<varlistentry>
|
||||
<term><varname>home.create.*</varname></term>
|
||||
|
||||
<listitem><para>If one or more credentials whose names begin with <literal>home.create.</literal>,
|
||||
followed by a valid UNIX username are passed, a new home area is created, one for each specified user
|
||||
record.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Kernel Command Line</title>
|
||||
|
||||
<variablelist class='kernel-commandline-options'>
|
||||
<varlistentry>
|
||||
<term><varname>systemd.firstboot=</varname></term>
|
||||
|
||||
<listitem><para>This boolean will disable the effect of <command>homectl firstboot</command>
|
||||
command. It's primarily interpreted by
|
||||
<citerefentry><refentrytitle>systemd-firstboot</refentrytitle><manvolnum>1</manvolnum></citerefentry>.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
|
@ -594,6 +594,8 @@
|
||||
|
||||
<listitem><para>Takes a boolean argument, 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
|
||||
|
@ -18,7 +18,7 @@ manpages = [
|
||||
'ENABLE_RESOLVE'],
|
||||
['environment.d', '5', [], 'ENABLE_ENVIRONMENT_D'],
|
||||
['file-hierarchy', '7', [], ''],
|
||||
['homectl', '1', [], 'ENABLE_HOMED'],
|
||||
['homectl', '1', ['systemd-homed-firstboot.service'], 'ENABLE_HOMED'],
|
||||
['homed.conf', '5', ['homed.conf.d'], 'ENABLE_HOMED'],
|
||||
['hostname', '5', [], ''],
|
||||
['hostnamectl', '1', [], 'ENABLE_HOSTNAMED'],
|
||||
|
@ -270,6 +270,16 @@
|
||||
<xi:include href="version-info.xml" xpointer="v254"/>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>home.create.*</varname></term>
|
||||
<listitem>
|
||||
<para>Creates a home area for the specified user with the user record data passed in. For details see
|
||||
<citerefentry><refentrytitle>homectl</refentrytitle><manvolnum>1</manvolnum></citerefentry>.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v256"/>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
|
@ -18,8 +18,6 @@ Environment=ASAN_OPTIONS=verify_asan_link_order=false
|
||||
@Incremental=yes
|
||||
@QemuMem=2G
|
||||
@RuntimeSize=8G
|
||||
# Make sure we don't trigger systemd-firstboot prompting for the root password.
|
||||
Credentials=passwd.plaintext-password.root=
|
||||
KernelCommandLineExtra=systemd.crash_shell
|
||||
systemd.log_level=debug
|
||||
systemd.log_ratelimit_kmsg=0
|
||||
@ -37,3 +35,4 @@ KernelCommandLineExtra=systemd.crash_shell
|
||||
selinux=0
|
||||
enforcing=0
|
||||
systemd.early_core_pattern=/core
|
||||
systemd.firstboot=no
|
||||
|
@ -1655,8 +1655,8 @@ static int run(int argc, char *argv[]) {
|
||||
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, terminating.");
|
||||
return 0; /* disabled */
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "cap-list.h"
|
||||
#include "capability-util.h"
|
||||
#include "cgroup-util.h"
|
||||
#include "creds-util.h"
|
||||
#include "dns-domain.h"
|
||||
#include "env-util.h"
|
||||
#include "fd-util.h"
|
||||
@ -35,7 +36,9 @@
|
||||
#include "percent-util.h"
|
||||
#include "pkcs11-util.h"
|
||||
#include "pretty-print.h"
|
||||
#include "proc-cmdline.h"
|
||||
#include "process-util.h"
|
||||
#include "recurse-dir.h"
|
||||
#include "rlimit-util.h"
|
||||
#include "spawn-polkit-agent.h"
|
||||
#include "terminal-util.h"
|
||||
@ -45,6 +48,7 @@
|
||||
#include "user-record-show.h"
|
||||
#include "user-record-util.h"
|
||||
#include "user-util.h"
|
||||
#include "userdb.h"
|
||||
#include "verbs.h"
|
||||
|
||||
static PagerFlags arg_pager_flags = 0;
|
||||
@ -80,6 +84,7 @@ 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_DESTRUCTOR_REGISTER(arg_identity_extra, json_variant_unrefp);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_identity_extra_this_machine, json_variant_unrefp);
|
||||
@ -1092,7 +1097,7 @@ static int add_disposition(JsonVariant **v) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int acquire_new_home_record(UserRecord **ret) {
|
||||
static int acquire_new_home_record(JsonVariant *input, UserRecord **ret) {
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
|
||||
_cleanup_(user_record_unrefp) UserRecord *hr = NULL;
|
||||
int r;
|
||||
@ -1102,12 +1107,16 @@ static int acquire_new_home_record(UserRecord **ret) {
|
||||
if (arg_identity) {
|
||||
unsigned line, column;
|
||||
|
||||
if (input)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Two identity records specified, refusing.");
|
||||
|
||||
r = json_parse_file(
|
||||
streq(arg_identity, "-") ? stdin : NULL,
|
||||
streq(arg_identity, "-") ? "<stdin>" : arg_identity, JSON_PARSE_SENSITIVE, &v, &line, &column);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse identity at %u:%u: %m", line, column);
|
||||
}
|
||||
} else
|
||||
v = json_variant_ref(input);
|
||||
|
||||
r = apply_identity_changes(&v);
|
||||
if (r < 0)
|
||||
@ -1146,7 +1155,18 @@ static int acquire_new_home_record(UserRecord **ret) {
|
||||
if (!hr)
|
||||
return log_oom();
|
||||
|
||||
r = user_record_load(hr, v, USER_RECORD_REQUIRE_REGULAR|USER_RECORD_ALLOW_SECRET|USER_RECORD_ALLOW_PRIVILEGED|USER_RECORD_ALLOW_PER_MACHINE|USER_RECORD_ALLOW_SIGNATURE|USER_RECORD_LOG|USER_RECORD_PERMISSIVE);
|
||||
r = user_record_load(
|
||||
hr,
|
||||
v,
|
||||
USER_RECORD_REQUIRE_REGULAR|
|
||||
USER_RECORD_ALLOW_SECRET|
|
||||
USER_RECORD_ALLOW_PRIVILEGED|
|
||||
USER_RECORD_ALLOW_PER_MACHINE|
|
||||
USER_RECORD_STRIP_BINDING|
|
||||
USER_RECORD_STRIP_STATUS|
|
||||
USER_RECORD_STRIP_SIGNATURE|
|
||||
USER_RECORD_LOG|
|
||||
USER_RECORD_PERMISSIVE);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@ -1247,7 +1267,7 @@ static int acquire_new_password(
|
||||
}
|
||||
}
|
||||
|
||||
static int create_home(int argc, char *argv[], void *userdata) {
|
||||
static int create_home_common(JsonVariant *input) {
|
||||
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
|
||||
_cleanup_(user_record_unrefp) UserRecord *hr = NULL;
|
||||
int r;
|
||||
@ -1258,36 +1278,7 @@ static int create_home(int argc, char *argv[], void *userdata) {
|
||||
|
||||
(void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
|
||||
|
||||
if (argc >= 2) {
|
||||
/* If a username was specified, use it */
|
||||
|
||||
if (valid_user_group_name(argv[1], 0))
|
||||
r = json_variant_set_field_string(&arg_identity_extra, "userName", argv[1]);
|
||||
else {
|
||||
_cleanup_free_ char *un = NULL, *rr = NULL;
|
||||
|
||||
/* Before we consider the user name invalid, let's check if we can split it? */
|
||||
r = split_user_name_realm(argv[1], &un, &rr);
|
||||
if (r < 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User name '%s' is not valid: %m", argv[1]);
|
||||
|
||||
if (rr) {
|
||||
r = json_variant_set_field_string(&arg_identity_extra, "realm", rr);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to set realm field: %m");
|
||||
}
|
||||
|
||||
r = json_variant_set_field_string(&arg_identity_extra, "userName", un);
|
||||
}
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to set userName field: %m");
|
||||
} else {
|
||||
/* If neither a username nor an identity have been specified we cannot operate. */
|
||||
if (!arg_identity)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User name required.");
|
||||
}
|
||||
|
||||
r = acquire_new_home_record(&hr);
|
||||
r = acquire_new_home_record(input, &hr);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@ -1374,6 +1365,41 @@ static int create_home(int argc, char *argv[], void *userdata) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int create_home(int argc, char *argv[], void *userdata) {
|
||||
int r;
|
||||
|
||||
if (argc >= 2) {
|
||||
/* If a username was specified, use it */
|
||||
|
||||
if (valid_user_group_name(argv[1], 0))
|
||||
r = json_variant_set_field_string(&arg_identity_extra, "userName", argv[1]);
|
||||
else {
|
||||
_cleanup_free_ char *un = NULL, *rr = NULL;
|
||||
|
||||
/* Before we consider the user name invalid, let's check if we can split it? */
|
||||
r = split_user_name_realm(argv[1], &un, &rr);
|
||||
if (r < 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User name '%s' is not valid: %m", argv[1]);
|
||||
|
||||
if (rr) {
|
||||
r = json_variant_set_field_string(&arg_identity_extra, "realm", rr);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to set realm field: %m");
|
||||
}
|
||||
|
||||
r = json_variant_set_field_string(&arg_identity_extra, "userName", un);
|
||||
}
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to set userName field: %m");
|
||||
} else {
|
||||
/* If neither a username nor an identity have been specified we cannot operate. */
|
||||
if (!arg_identity)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User name required.");
|
||||
}
|
||||
|
||||
return create_home_common(/* input= */ NULL);
|
||||
}
|
||||
|
||||
static int remove_home(int argc, char *argv[], void *userdata) {
|
||||
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
|
||||
int r, ret = 0;
|
||||
@ -2131,6 +2157,190 @@ static int rebalance(int argc, char *argv[], void *userdata) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int create_from_credentials(void) {
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
int ret = 0, n_created = 0, r;
|
||||
|
||||
fd = open_credentials_dir();
|
||||
if (IN_SET(fd, -ENXIO, -ENOENT)) /* Credential env var not set, or dir doesn't exist. */
|
||||
return 0;
|
||||
if (fd < 0)
|
||||
return log_error_errno(fd, "Failed to open credentials directory: %m");
|
||||
|
||||
_cleanup_free_ DirectoryEntries *des = NULL;
|
||||
r = readdir_all(fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE, &des);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to enumerate credentials: %m");
|
||||
|
||||
FOREACH_ARRAY(i, des->entries, des->n_entries) {
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *identity = NULL;
|
||||
struct dirent *de = *i;
|
||||
const char *e;
|
||||
|
||||
if (de->d_type != DT_REG)
|
||||
continue;
|
||||
|
||||
e = startswith(de->d_name, "home.create.");
|
||||
if (!e)
|
||||
continue;
|
||||
|
||||
if (!valid_user_group_name(e, 0)) {
|
||||
log_notice("Skipping over credential with name that is not a suitable user name: %s", de->d_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
r = json_parse_file_at(
|
||||
/* f= */ NULL,
|
||||
fd,
|
||||
de->d_name,
|
||||
/* flags= */ 0,
|
||||
&identity,
|
||||
/* ret_line= */ NULL,
|
||||
/* ret_column= */ NULL);
|
||||
if (r < 0) {
|
||||
log_warning_errno(r, "Failed to parse user record in credential '%s', ignoring: %m", de->d_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
JsonVariant *un;
|
||||
un = json_variant_by_key(identity, "userName");
|
||||
if (un) {
|
||||
if (!json_variant_is_string(un)) {
|
||||
log_warning("User record from credential '%s' contains 'userName' field of invalid type, ignoring.", de->d_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!streq(json_variant_string(un), e)) {
|
||||
log_warning("User record from credential '%s' contains 'userName' field (%s) that doesn't match credential name (%s), ignoring.", de->d_name, json_variant_string(un), e);
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
r = json_variant_set_field_string(&identity, "userName", e);
|
||||
if (r < 0)
|
||||
return log_warning_errno(r, "Failed to set userName field: %m");
|
||||
}
|
||||
|
||||
log_notice("Processing user '%s' from credentials.", e);
|
||||
|
||||
r = create_home_common(identity);
|
||||
if (r >= 0)
|
||||
n_created++;
|
||||
|
||||
RET_GATHER(ret, r);
|
||||
}
|
||||
|
||||
return ret < 0 ? ret : n_created;
|
||||
}
|
||||
|
||||
static int has_regular_user(void) {
|
||||
_cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
|
||||
int r;
|
||||
|
||||
r = userdb_all(USERDB_SUPPRESS_SHADOW, &iterator);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to create user enumerator: %m");
|
||||
|
||||
for (;;) {
|
||||
_cleanup_(user_record_unrefp) UserRecord *ur = NULL;
|
||||
|
||||
r = userdb_iterator_get(iterator, &ur);
|
||||
if (r == -ESRCH)
|
||||
break;
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to enumerate users: %m");
|
||||
|
||||
if (user_record_disposition(ur) == USER_REGULAR)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int create_interactively(void) {
|
||||
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
|
||||
_cleanup_free_ char *username = NULL;
|
||||
int r;
|
||||
|
||||
if (!arg_prompt_new_user) {
|
||||
log_debug("Prompting for user creation was not requested.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = acquire_bus(&bus);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
(void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
|
||||
|
||||
(void) reset_terminal_fd(STDIN_FILENO, /* switch_to_text= */ false);
|
||||
|
||||
for (;;) {
|
||||
username = mfree(username);
|
||||
|
||||
r = ask_string(&username,
|
||||
"%s Please enter user name to create (empty to skip): ",
|
||||
special_glyph(SPECIAL_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, 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 = json_variant_set_field_string(&arg_identity_extra, "userName", username);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to set userName field: %m");
|
||||
|
||||
return create_home_common(/* input= */ NULL);
|
||||
}
|
||||
|
||||
static int verb_firstboot(int argc, char *argv[], void *userdata) {
|
||||
int r;
|
||||
|
||||
/* 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);
|
||||
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.");
|
||||
arg_prompt_new_user = false;
|
||||
}
|
||||
|
||||
r = create_from_credentials();
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r > 0) /* Already created users from credentials */
|
||||
return 0;
|
||||
|
||||
r = has_regular_user();
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r > 0) {
|
||||
log_info("Regular user already present in user database, skipping user creation.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return create_interactively();
|
||||
}
|
||||
|
||||
static int drop_from_identity(const char *field) {
|
||||
int r;
|
||||
|
||||
@ -2187,6 +2397,7 @@ static int help(int argc, char *argv[], void *userdata) {
|
||||
" deactivate-all Deactivate all active home areas\n"
|
||||
" rebalance Rebalance free space between home areas\n"
|
||||
" with USER [COMMAND…] Run shell or command with access to a home area\n"
|
||||
" firstboot Run first-boot home area creation wizard\n"
|
||||
"\n%4$sOptions:%5$s\n"
|
||||
" -h --help Show this help\n"
|
||||
" --version Show package version\n"
|
||||
@ -2205,6 +2416,8 @@ 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"
|
||||
"\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"
|
||||
@ -2412,6 +2625,7 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
ARG_FIDO2_CRED_ALG,
|
||||
ARG_CAPABILITY_BOUNDING_SET,
|
||||
ARG_CAPABILITY_AMBIENT_SET,
|
||||
ARG_PROMPT_NEW_USER,
|
||||
};
|
||||
|
||||
static const struct option options[] = {
|
||||
@ -2504,6 +2718,7 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
{ "rebalance-weight", required_argument, NULL, ARG_REBALANCE_WEIGHT },
|
||||
{ "capability-bounding-set", required_argument, NULL, ARG_CAPABILITY_BOUNDING_SET },
|
||||
{ "capability-ambient-set", required_argument, NULL, ARG_CAPABILITY_AMBIENT_SET },
|
||||
{ "prompt-new-user", no_argument, NULL, ARG_PROMPT_NEW_USER },
|
||||
{}
|
||||
};
|
||||
|
||||
@ -3788,6 +4003,10 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
break;
|
||||
}
|
||||
|
||||
case ARG_PROMPT_NEW_USER:
|
||||
arg_prompt_new_user = true;
|
||||
break;
|
||||
|
||||
case '?':
|
||||
return -EINVAL;
|
||||
|
||||
@ -3854,6 +4073,7 @@ static int run(int argc, char *argv[]) {
|
||||
{ "lock-all", VERB_ANY, 1, 0, lock_all_homes },
|
||||
{ "deactivate-all", VERB_ANY, 1, 0, deactivate_all_homes },
|
||||
{ "rebalance", VERB_ANY, 1, 0, rebalance },
|
||||
{ "firstboot", VERB_ANY, 1, 0, verb_firstboot },
|
||||
{}
|
||||
};
|
||||
|
||||
|
@ -100,6 +100,17 @@ int get_encrypted_credentials_dir(const char **ret) {
|
||||
return get_credentials_dir_internal("ENCRYPTED_CREDENTIALS_DIRECTORY", ret);
|
||||
}
|
||||
|
||||
int open_credentials_dir(void) {
|
||||
const char *d;
|
||||
int r;
|
||||
|
||||
r = get_credentials_dir(&d);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return RET_NERRNO(open(d, O_CLOEXEC|O_DIRECTORY));
|
||||
}
|
||||
|
||||
int read_credential(const char *name, void **ret, size_t *ret_size) {
|
||||
_cleanup_free_ char *fn = NULL;
|
||||
const char *d;
|
||||
|
@ -31,6 +31,8 @@ bool credential_glob_valid(const char *s);
|
||||
int get_credentials_dir(const char **ret);
|
||||
int get_encrypted_credentials_dir(const char **ret);
|
||||
|
||||
int open_credentials_dir(void);
|
||||
|
||||
/* Where creds have been passed to the system */
|
||||
#define SYSTEM_CREDENTIALS_DIRECTORY "/run/credentials/@system"
|
||||
#define ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY "/run/credentials/@encrypted"
|
||||
|
@ -303,6 +303,10 @@ units = [
|
||||
'file' : 'systemd-homed-activate.service',
|
||||
'conditions' : ['ENABLE_HOMED'],
|
||||
},
|
||||
{
|
||||
'file' : 'systemd-homed-firstboot.service',
|
||||
'conditions' : ['ENABLE_HOMED'],
|
||||
},
|
||||
{
|
||||
'file' : 'systemd-homed.service.in',
|
||||
'conditions' : ['ENABLE_HOMED'],
|
||||
|
28
units/systemd-homed-firstboot.service
Normal file
28
units/systemd-homed-firstboot.service
Normal file
@ -0,0 +1,28 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
#
|
||||
# This file is part of systemd.
|
||||
#
|
||||
# systemd is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
[Unit]
|
||||
Description=First Boot Home Area Wizard
|
||||
Documentation=man:homectl(1)
|
||||
ConditionFirstBoot=yes
|
||||
After=home.mount systemd-homed.service
|
||||
Before=systemd-user-sessions.service first-boot-complete.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
ExecStart=homectl firstboot --prompt-new-user
|
||||
StandardOutput=tty
|
||||
StandardInput=tty
|
||||
StandardError=tty
|
||||
ImportCredential=home.*
|
||||
|
||||
[Install]
|
||||
WantedBy=systemd-homed.service
|
||||
Also=systemd-homed.service
|
@ -39,4 +39,4 @@ TimeoutStopSec=3min
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Alias=dbus-org.freedesktop.home1.service
|
||||
Also=systemd-homed-activate.service systemd-userdbd.service
|
||||
Also=systemd-homed-activate.service systemd-userdbd.service systemd-homed-firstboot.service
|
||||
|
Loading…
Reference in New Issue
Block a user