systemd-sysinstall (this time on top of bootctl link) (#41877)

This is a reiteration of #38764, but this time on top of "bootctl link"
for installing a kernel, i.e. #41543, instead of "kernel-install".

It's the same as #41543, only adds three new commits on top.
This commit is contained in:
Lennart Poettering
2026-05-05 17:20:47 +02:00
committed by GitHub
19 changed files with 2142 additions and 30 deletions
+11 -12
View File
@@ -186,6 +186,17 @@ SPDX-License-Identifier: LGPL-2.1-or-later
use as additional search condition. Use case: images that combined a sysext
partition with a portable service partition in one.
- **systemd-sysinstall:**
- make systemd-sysinstall itself a varlink service
- read installation definition from json file
- polkit support in sysinstall
- sysinstall: permit driving installer via credentials
- add --offline=no mode where we talk to socket based services rather than forking off
- if a user doesn't pick a locale during boot into installer, don't ask again after install, because we suppressed credential propagation
- repart: add MatchLabel= which matches against partition label, so that we
truly can install different images in parallel
- add "systemctl wait" or so, which does what "systemd-run --wait" does, but
for all units. It should be both a way to pin units into memory as well as a
wait to retrieve their exit data.
@@ -1200,14 +1211,6 @@ SPDX-License-Identifier: LGPL-2.1-or-later
- introduce a new group to own TPM devices
- introduce a small "systemd-installer" tool or so, that glues
systemd-repart-as-installer and bootctl-install into one. Would just
interactively ask user for target disk (with completion and so on), and then do
two varlink calls to the the two tools with the right parameters. To support
"offline" operation, optionally invoke the two tools directly as child
processes with varlink communication over socketpair(). This all should be
useful as blueprint for graphical installers which should do the same.
- introduce an option (or replacement) for "systemctl show" that outputs all
properties as JSON, similar to busctl's new JSON output. In contrast to that
it should skip the variant type string though.
@@ -2491,10 +2494,6 @@ SPDX-License-Identifier: LGPL-2.1-or-later
that we can sanely copy ESP contents, /usr/ images, and then set up btrfs
raid for the root fs to extend/mirror the existing install. This would be
very similar to the concept of live-install-through-btrfs-migration.
- add --installer or so, that will interactively ask for a
target disk, maybe ask for confirmation, and install something on disk. Then,
hook that into installer.target or so, so that it can be used to
install/replicate installs
- should probably enable btrfs' "temp_fsid" feature for all file
systems it creates, as we have no interest in RAID for repart, and it should
make sure that we can mount them trivially everywhere.
+4
View File
@@ -1218,6 +1218,10 @@ manpages = [
'systemd-sysext-sysroot.service',
'systemd-sysext.service'],
'ENABLE_SYSEXT'],
['systemd-sysinstall',
'8',
['systemd-sysinstall.service'],
'ENABLE_SYSINSTALL'],
['systemd-system-update-generator', '8', [], ''],
['systemd-system.conf',
'5',
+292
View File
@@ -0,0 +1,292 @@
<?xml version='1.0'?> <!--*-nxml-*-->
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
<refentry id="systemd-sysinstall" conditional='ENABLE_SYSINSTALL'
xmlns:xi="http://www.w3.org/2001/XInclude">
<refentryinfo>
<title>systemd-sysinstall</title>
<productname>systemd</productname>
</refentryinfo>
<refmeta>
<refentrytitle>systemd-sysinstall</refentrytitle>
<manvolnum>8</manvolnum>
</refmeta>
<refnamediv>
<refname>systemd-sysinstall</refname>
<refname>systemd-sysinstall.service</refname>
<refpurpose>Simple OS installer</refpurpose>
</refnamediv>
<refsynopsisdiv>
<cmdsynopsis>
<command>systemd-sysinstall</command>
<arg choice="opt" rep="repeat">OPTIONS</arg>
<arg choice="opt">BLOCKDEVICE</arg>
</cmdsynopsis>
<para><filename>systemd-sysinstall.service</filename></para>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para><command>systemd-sysinstall</command> is a simple terminal and command line based operating system
installer tool. Its primary use-case is to act as an automatically started interactive interface when
booting from an installer medium (e.g. a USB stick), in order to install an OS onto a target
disk. However, it may also be invoked directly from a shell. It executes the following steps:</para>
<orderedlist>
<listitem><para>It prompts the user for the target disk to install the OS on. (Unless the block device
is already specified on the command line.)</para></listitem>
<listitem><para>It validates whether the disk is suitable (i.e. large enough, and with enough
free/unpartitioned space) for an OS installation. If it is generally suitable the user is prompted if they
want to erase the disk before installation, or if the OS shall be added to the existing partitions on
the disk (the latter only if enough free/unpartitioned disk space is available).</para></listitem>
<listitem><para>It prompts the user whether to register the newly installed OS with the firmware boot option menu.</para></listitem>
<listitem><para>It requests confirmation from the user, after showing a summary of the planned OS installation.</para></listitem>
<listitem><para>It invokes
<citerefentry><refentrytitle>systemd-creds</refentrytitle><manvolnum>1</manvolnum></citerefentry>'s
<command>encrypt</command> command in order to generate encrypted (TPM locked, if available) system
credential files for a few, very basic system settings of the currently booted system (locale, keymap,
timezone), which it will install on the target disk, parameterizing the invoked kernel. (Or in other
words, it prepares that some settings already in effect on the installer system are propagated securely
onto the new installation.)</para></listitem>
<listitem><para>It invokes
<citerefentry><refentrytitle>systemd-repart</refentrytitle><manvolnum>8</manvolnum></citerefentry> with
a definitions directory of <filename>/usr/lib/repart.sysinstall.d/</filename> (only if populated – if
not will use the default of <filename>/usr/lib/repart.d/</filename>). This is supposed to set up the
basic OS partition structure on the target disk and copies in basic OS partitions (most importantly the
<filename>/usr/</filename> hierarchy).</para></listitem>
<listitem><para>It invokes
<citerefentry><refentrytitle>bootctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>'s
<command>link</command> command to install an OS kernel image onto the target disk's ESP/XBOOTLDR,
together with the credential files prepared earlier.</para></listitem>
<listitem><para>It invokes
<citerefentry><refentrytitle>bootctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>'s
<command>install</command> command to install the
<citerefentry><refentrytitle>systemd-boot</refentrytitle><manvolnum>7</manvolnum></citerefentry> boot
loader onto the target disk's ESP.</para></listitem>
<listitem><para>After confirmation, it reboots the system.</para></listitem>
</orderedlist>
<para>Note that the prompts/confirmation may be disabled via the command line, enabling fully automatic,
non-interactive installation. See below.</para>
<para>Note this tool does not interactively query the user for a user to create or a root password to be
set on the target system, under the assumption these questions are better prompted from within the newly
installed system's first boot process, for example via the
<citerefentry><refentrytitle>systemd-firstboot</refentrytitle><manvolnum>1</manvolnum></citerefentry> or
<filename>systemd-homed-firstboot.service</filename> components. Note that if required such settings
may be propagated explicitly via the <option>--load-credential=</option> switch below.</para>
</refsect1>
<refsect1>
<title>Options</title>
<para>The following options are understood:</para>
<variablelist>
<varlistentry>
<term><option>--definitions=</option></term>
<listitem><para>Overrides the directory where <command>systemd-repart</command> shall read its
partition definitions from, in place of the default of
<filename>/usr/lib/repart.sysinstall.d/</filename>.</para>
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--welcome=</option></term>
<listitem><para>Takes a boolean argument. Controls whether to show the brief welcome text normally
displayed at the beginning of the installation. Defaults to true.</para>
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--chrome=</option></term>
<listitem><para>Takes a boolean argument. Controls whether to show the colored bars at the top and
bottom of the terminal interface. Defaults to true.</para>
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--erase=</option></term>
<listitem><para>Takes a boolean argument. Controls whether to erase the current contents of the
target disk. If this switch is not used the user is prompted.</para>
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--confirm=</option></term>
<listitem><para>Takes a boolean argument. Controls whether to interactively query the user for
confirmation before initiating the OS installation. Defaults to true.</para>
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--reboot=</option></term>
<listitem><para>Takes a boolean argument. Controls whether to reboot the system after completing the
installation. Defaults to false.</para>
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--variables=</option></term>
<listitem><para>Takes a boolean argument. Controls whether to register the installed boot loader in
the firmware's boot options database. If not specified the user will be prompted.</para>
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--summary=</option></term>
<listitem><para>Takes a boolean argument. Controls whether to show a summary of the choices made
before asking for confirmation to proceed with the OS installation. Defaults to true.</para>
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--kernel=</option></term>
<listitem><para>Takes a path to a unified kernel image (UKI). Explicitly selects the kernel image to
install on the target disk. If unspecified the currently booted kernel image is installed on the
target disk.</para>
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--set-credential=<replaceable>id</replaceable>:<replaceable>value</replaceable></option></term>
<listitem><para>Accepts an additional system credential to encrypt (with a key generated on the local
TPM, if available, and the null key otherwise) and place next to the installed kernel image in the
ESP. This may be used to parameterize the installed kernel with arbitrary system credentials. Do not
use this switch for sensitive data (such as passwords), use <option>--load-credential=</option>
instead, see below. May be used multiple times to configure multiple credentials.</para>
<para>Note that three system credentials are propagated in similar fashion to the target system:
the locale, keymap and timezone. This may be controlled by the relevant
<option>--copy-locale=</option>, <option>--copy-keymap=</option> and <option>--copy-timezone=</option>
options below.</para>
<para>See
<citerefentry><refentrytitle>systemd.system-credentials</refentrytitle><manvolnum>7</manvolnum></citerefentry>
for a list of well-known system credentials that may be propagated this way. (Note that you may pass
arbitrary additional credentials this way, that can be consumed by any service of your
choice, via the usual system credentials logic.)</para>
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--load-credential=<replaceable>id</replaceable>:<replaceable>path</replaceable></option></term>
<listitem><para>Similar to <option>--set-credential=</option> but reads the credential value from a
file on disk or an <constant>AF_UNIX</constant> socket in the file system. This is generally
preferable for sensitive data, such as passwords.</para>
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--copy-locale=</option></term>
<term><option>--copy-keymap=</option></term>
<term><option>--copy-timezone=</option></term>
<listitem><para>These options take boolean parameters. They control whether the indicated system
settings shall be propagated from the currently running system into the new target OS
installation. These options default to true.</para>
<para>Typically, these three settings are the minimal settings that need to be configured during early
boot of an installer medium in order to make the installer tool accessible to the user. The
<citerefentry><refentrytitle>systemd-firstboot</refentrytitle><manvolnum>1</manvolnum></citerefentry>
tool may be used to query the user interactively when the OS install medium is booted for these
properties. By propagating these settings to the target installation via system credentials they do
not need to be queried again on first boot of the new installation.</para>
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--mute-console=</option></term>
<listitem><para>Takes a boolean argument. Controls whether to disable kernel and service manager log
output to the console the installer is invoked on temporarily while running, in order to avoid
interleaved output. Defaults to false.</para>
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="help" />
<xi:include href="standard-options.xml" xpointer="version" />
</variablelist>
</refsect1>
<refsect1>
<title>Exit status</title>
<para>On success, 0 is returned, and a non-zero failure code otherwise.</para>
</refsect1>
<refsect1>
<title>Example</title>
<example>
<title>Invoke the tool for a fully automatic non-interactive OS installation</title>
<programlisting>systemd-sysinstall \
/dev/disk/by-id/nvme-Micron_MTFDKBA1T0TFH_214532D0CDA5 \
--erase=yes \
--confirm=no \
--variables=yes \
--load-credential=ssh.authorized_keys.root:my-ssh-key
</programlisting>
<para>This installs the OS on the selected disk, erasing any previous contents, without confirmation,
registers it in the firmware, and drops in the SSH key for the root user, read from the
<filename>my-ssh-key</filename> file in the current directory.</para>
</example>
</refsect1>
<refsect1>
<title>See Also</title>
<para><simplelist type="inline">
<member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-creds</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-repart</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>bootctl</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-firstboot</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-boot</refentrytitle><manvolnum>7</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd.system-credentials</refentrytitle><manvolnum>7</manvolnum></citerefentry></member>
</simplelist></para>
</refsect1>
</refentry>
+3
View File
@@ -1582,6 +1582,7 @@ foreach tuple : [
['rfkill'],
['smack'],
['sysext'],
['sysinstall'],
['sysusers'],
['timedated'],
['timesyncd'],
@@ -2144,6 +2145,7 @@ subdir('src/storagetm')
subdir('src/sulogin-shell')
subdir('src/sysctl')
subdir('src/sysext')
subdir('src/sysinstall')
subdir('src/system-update-generator')
subdir('src/systemctl')
subdir('src/sysupdate')
@@ -2928,6 +2930,7 @@ foreach tuple : [
['resolve'],
['rfkill'],
['sysext'],
['sysinstall'],
['systemd-analyze', conf.get('ENABLE_ANALYZE') == 1],
['sysupdate'],
['sysupdated'],
+2
View File
@@ -109,6 +109,8 @@ option('sysupdate', type : 'feature', deprecated : { 'true' : 'enabled', 'false'
option('sysupdated', type: 'combo', value : 'auto',
choices : ['auto', 'enabled', 'disabled'],
description : 'install the systemd-sysupdated service')
option('sysinstall', type : 'boolean',
description : 'install the systemd-sysinstall tool')
option('coredump', type : 'boolean',
description : 'install the coredump handler')
+1
View File
@@ -54,6 +54,7 @@ foreach item : [
['systemd-resolve', 'ENABLE_RESOLVE'],
['systemd-run', ''],
['systemd-sysext', 'ENABLE_SYSEXT'],
['systemd-sysinstall', 'ENABLE_SYSINSTALL'],
['systemd-vmspawn', 'ENABLE_VMSPAWN'],
['systemd-vpick', ''],
['timedatectl', 'ENABLE_TIMEDATED'],
+90
View File
@@ -0,0 +1,90 @@
# shellcheck shell=bash
# systemd-sysinstall(8) completion -*- shell-script -*-
# 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.
#
# systemd is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with systemd; If not, see <https://www.gnu.org/licenses/>.
__contains_word() {
local w word=$1; shift
for w in "$@"; do
[[ $w = "$word" ]] && return
done
}
__get_block_devices() {
systemd-repart --list-devices 2>/dev/null
}
_systemd_sysinstall() {
local comps
local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} words cword
local -A OPTS=(
[STANDALONE]='-h --help --version'
[ARG]='--welcome
--chrome
--erase
--confirm
--summary
--reboot
--variables
--mute-console
--copy-locale
--copy-keymap
--copy-timezone
--definitions
--kernel
--set-credential
--load-credential'
)
_init_completion || return
if __contains_word "$prev" ${OPTS[ARG]}; then
case $prev in
--welcome|--chrome|--confirm|--summary|--reboot|--mute-console|--copy-locale|--copy-keymap|--copy-timezone)
comps='yes no'
;;
--erase|--variables)
comps='yes no auto'
;;
--definitions)
comps=$(compgen -A directory -- "$cur")
compopt -o filenames
;;
--kernel|--load-credential)
comps=$(compgen -A file -- "$cur")
compopt -o filenames
;;
--set-credential)
comps=''
;;
esac
COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
return 0
fi
if [[ "$cur" = -* ]]; then
COMPREPLY=( $(compgen -W '${OPTS[*]}' -- "$cur") )
return 0
fi
comps=$(__get_block_devices)
COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
compopt -o filenames
return 0
}
complete -F _systemd_sysinstall systemd-sysinstall
+29
View File
@@ -0,0 +1,29 @@
#compdef systemd-sysinstall
# SPDX-License-Identifier: LGPL-2.1-or-later
(( $+functions[_systemd-sysinstall_devices] )) ||
_systemd-sysinstall_devices() {
local -a _devices
_devices=( ${(f)"$(systemd-repart --list-devices 2>/dev/null)"} )
_wanted devices expl 'block device' compadd -a _devices
}
_arguments \
'(- *)'{-h,--help}'[Show help text]' \
'(- *)--version[Show package version]' \
'--welcome=[Show welcome text]:boolean:(yes no)' \
'--chrome=[Show colored bars at top and bottom of the terminal]:boolean:(yes no)' \
'--erase=[Erase target disk before installation]:boolean:(yes no auto)' \
'--confirm=[Query for confirmation before installation]:boolean:(yes no)' \
'--summary=[Show summary before installation]:boolean:(yes no)' \
'--reboot=[Reboot system after installation]:boolean:(yes no)' \
'--variables=[Register installation in firmware variables]:boolean:(yes no auto)' \
'--mute-console=[Mute kernel/PID 1 console output during installation]:boolean:(yes no)' \
'--copy-locale=[Copy current locale to target system]:boolean:(yes no)' \
'--copy-keymap=[Copy current keymap to target system]:boolean:(yes no)' \
'--copy-timezone=[Copy current timezone to target system]:boolean:(yes no)' \
'--definitions=[Find partition definitions in directory]:directory:_directories' \
'--kernel=[Kernel image to install]:kernel image:_files' \
'--set-credential=[Install a credential with a literal value]: : _message "ID:VALUE"' \
'--load-credential=[Load credential from a file or AF_UNIX socket]: : _message "ID:PATH"' \
'*::block device:_systemd-sysinstall_devices'
+1
View File
@@ -43,6 +43,7 @@ foreach item : [
['_systemd-nspawn', ''],
['_systemd-path', ''],
['_systemd-run', ''],
['_systemd-sysinstall', 'ENABLE_SYSINSTALL'],
['_systemd-tmpfiles', 'ENABLE_TMPFILES'],
['_timedatectl', 'ENABLE_TIMEDATED'],
['_udevadm', ''],
+10
View File
@@ -1697,6 +1697,16 @@ int get_timezone(char **ret) {
return strdup_to(ret, e);
}
int get_timezone_prefer_env(char **ret) {
assert(ret);
const char *e = getenv("TZ");
if (e && e[0] == ':' && timezone_is_valid(e + 1, LOG_DEBUG))
return strdup_to(ret, e + 1);
return get_timezone(ret);
}
const char* etc_localtime(void) {
static const char *cached = NULL;
+1
View File
@@ -178,6 +178,7 @@ bool clock_supported(clockid_t clock);
usec_t usec_shift_clock(usec_t x, clockid_t from, clockid_t to);
int get_timezone(char **ret);
int get_timezone_prefer_env(char **ret);
const char* etc_localtime(void);
int mktime_or_timegm_usec(struct tm *tm, bool utc, usec_t *ret);
+33 -16
View File
@@ -25,6 +25,7 @@
#include "hashmap.h"
#include "id128-util.h"
#include "io-util.h"
#include "iovec-util.h"
#include "json-util.h"
#include "kernel-image.h"
#include "log.h"
@@ -44,6 +45,7 @@ typedef struct ExtraFile {
/* The source and the temporary file we copy it into */
int source_fd, temp_fd;
char *filename, *temp_filename;
struct iovec data; /* Alternative to 'source_fd': literal data */
} ExtraFile;
#define EXTRA_FILE_NULL \
@@ -115,6 +117,7 @@ static void extra_file_done(ExtraFile *x) {
x->temp_fd = safe_close(x->temp_fd);
x->filename = mfree(x->filename);
x->temp_filename = mfree(x->temp_filename);
iovec_done(&x->data);
}
static void profile_done(Profile *p) {
@@ -366,7 +369,8 @@ static int link_context_pick_entry_token(LinkContext *c) {
}
static int begin_copy_file(
int source_fd,
int source_fd, /* Either the source fd is specified, or the 'data' below, not both */
const struct iovec *data,
const char *filename,
int target_dir_fd,
int *ret_tmpfile_fd,
@@ -374,7 +378,6 @@ static int begin_copy_file(
int r;
assert(source_fd >= 0);
assert(filename);
assert(target_dir_fd >= 0);
assert(ret_tmpfile_fd);
@@ -398,11 +401,18 @@ static int begin_copy_file(
CLEANUP_TMPFILE_AT(target_dir_fd, t);
r = copy_bytes(source_fd, write_fd, UINT64_MAX, COPY_REFLINK|COPY_SEEK0_SOURCE);
if (r < 0)
return log_error_errno(r, "Failed to copy data into '%s': %m", filename);
if (source_fd >= 0) {
r = copy_bytes(source_fd, write_fd, UINT64_MAX, COPY_REFLINK|COPY_SEEK0_SOURCE);
if (r < 0)
return log_error_errno(r, "Failed to copy data into '%s': %m", filename);
(void) copy_times(source_fd, write_fd, /* flags= */ 0);
} else if (iovec_is_set(data)) {
r = loop_write(write_fd, data->iov_base, data->iov_len);
if (r < 0)
return log_error_errno(r, "Failed to write data into '%s': %m", filename);
}
(void) copy_times(source_fd, write_fd, /* flags= */ 0);
(void) fchmod(write_fd, 0644);
*ret_tmpfile_fd = TAKE_FD(write_fd);
@@ -824,6 +834,7 @@ static int run_link_now(LinkContext *c) {
r = begin_copy_file(
c->kernel_fd,
/* data= */ NULL,
c->kernel_filename,
c->entry_token_dir_fd,
&c->kernel_temp_fd,
@@ -834,6 +845,7 @@ static int run_link_now(LinkContext *c) {
FOREACH_ARRAY(x, c->extra, c->n_extra) {
r = begin_copy_file(
x->source_fd,
&x->data,
x->filename,
c->entry_token_dir_fd,
&x->temp_fd,
@@ -1043,7 +1055,8 @@ static int dispatch_extras(const char *name, sd_json_variant *v, sd_json_dispatc
static const sd_json_dispatch_field dispatch_table[] = {
{ "filename", SD_JSON_VARIANT_STRING, json_dispatch_loader_entry_resource_filename, offsetof(ExtraParameters, extra_file.filename), SD_JSON_MANDATORY },
{ "fileDescriptor", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, offsetof(ExtraParameters, fd_index), SD_JSON_MANDATORY },
{ "fileDescriptor", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, offsetof(ExtraParameters, fd_index), 0 },
{ "data", SD_JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(ExtraParameters, extra_file.data), 0 },
{},
};
@@ -1051,17 +1064,21 @@ static int dispatch_extras(const char *name, sd_json_variant *v, sd_json_dispatc
if (r < 0)
return r;
xp.extra_file.source_fd = sd_varlink_peek_dup_fd(c->link, xp.fd_index);
if (xp.extra_file.source_fd < 0)
return log_debug_errno(xp.extra_file.source_fd, "Failed to acquire extra fd from Varlink: %m");
r = fd_verify_safe_flags(xp.extra_file.source_fd);
if (r < 0)
if (iovec_is_set(&xp.extra_file.data) == (xp.fd_index != UINT_MAX))
return sd_varlink_error_invalid_parameter_name(c->link, name);
if (xp.fd_index != UINT_MAX) {
xp.extra_file.source_fd = sd_varlink_peek_dup_fd(c->link, xp.fd_index);
if (xp.extra_file.source_fd < 0)
return log_debug_errno(xp.extra_file.source_fd, "Failed to acquire extra fd from Varlink: %m");
r = fd_verify_regular(xp.extra_file.source_fd);
if (r < 0)
return log_debug_errno(r, "Failed to validate that the extra file is a regular file descriptor: %m");
r = fd_verify_safe_flags(xp.extra_file.source_fd);
if (r < 0)
return sd_varlink_error_invalid_parameter_name(c->link, name);
r = fd_verify_regular(xp.extra_file.source_fd);
if (r < 0)
return log_debug_errno(r, "Failed to validate that the extra file is a regular file descriptor: %m");
}
if (!GREEDY_REALLOC(c->context.extra, c->context.n_extra+1))
return log_oom();
+4 -2
View File
@@ -153,8 +153,10 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE(
BootEntryExtraFile,
SD_VARLINK_FIELD_COMMENT("The name of the extra file"),
SD_VARLINK_DEFINE_FIELD(filename, SD_VARLINK_STRING, 0),
SD_VARLINK_FIELD_COMMENT("Index into array of file descriptors, pointing to a file descriptor referencing the extra file."),
SD_VARLINK_DEFINE_FIELD(fileDescriptor, SD_VARLINK_INT, 0));
SD_VARLINK_FIELD_COMMENT("Index into array of file descriptors, pointing to a file descriptor referencing the extra file to copy in. Either this or the 'data' field below must be set – not both, not neither."),
SD_VARLINK_DEFINE_FIELD(fileDescriptor, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Literal data to place in the extra file."),
SD_VARLINK_DEFINE_FIELD(data, SD_VARLINK_STRING, SD_VARLINK_NULLABLE));
static SD_VARLINK_DEFINE_METHOD(
Link,
+10
View File
@@ -0,0 +1,10 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
executables += [
executable_template + {
'name' : 'systemd-sysinstall',
'public' : true,
'conditions' : ['ENABLE_SYSINSTALL'],
'sources' : files('sysinstall.c'),
},
]
File diff suppressed because it is too large Load Diff
+183
View File
@@ -0,0 +1,183 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
set -eux
set -o pipefail
if ! command -v systemd-sysinstall >/dev/null; then
echo "systemd-sysinstall not found, skipping."
exit 0
fi
if ! command -v systemd-repart >/dev/null; then
echo "systemd-repart not found, skipping."
exit 0
fi
if ! command -v bootctl >/dev/null; then
echo "bootctl not found, skipping."
exit 0
fi
if ! command -v ukify >/dev/null; then
echo "ukify not found, skipping."
exit 0
fi
if [[ ! -d /usr/lib/systemd/boot/efi ]]; then
echo "sd-boot is not installed, skipping."
exit 0
fi
# We need a real environment to fiddle with loop devices.
if systemd-detect-virt -cq; then
echo "Running in a container, skipping."
exit 0
fi
# shellcheck source=test/units/util.sh
. "$(dirname "$0")"/util.sh
WORKDIR="$(mktemp --directory /tmp/test-sysinstall.XXXXXXXXXX)"
LOOPDEV=""
MOUNTED=0
cleanup() {
set +e
if [[ "$MOUNTED" -eq 1 ]]; then
umount -R "$WORKDIR/mnt"
MOUNTED=0
fi
if [[ -n "$LOOPDEV" ]]; then
systemd-dissect --detach "$LOOPDEV"
LOOPDEV=""
fi
rm -rf "$WORKDIR"
}
trap cleanup EXIT
# 1) Build a small fake "OS source" tree. systemd-sysinstall picks this up via
# the repart.sysinstall.d definitions: CopyFiles= seeds the new root
# partition with these files.
SOURCE_ROOT="$WORKDIR/sourceroot"
mkdir -p "$SOURCE_ROOT/usr/lib" "$SOURCE_ROOT/etc"
cat >"$SOURCE_ROOT/usr/lib/os-release" <<'EOF'
ID=testos
NAME="Test OS"
PRETTY_NAME="Test OS for systemd-sysinstall"
VERSION_ID=1
EOF
ln -s ../usr/lib/os-release "$SOURCE_ROOT/etc/os-release"
# 2) Build a minimal UKI. bootctl link only requires a valid PE with .osrel and
# the systemd-stub SBAT marker, so the .linux/.initrd contents do not need
# to be a real kernel.
echo "fake-kernel" >"$WORKDIR/vmlinuz"
echo "fake-initrd" >"$WORKDIR/initrd"
ukify build \
--linux "$WORKDIR/vmlinuz" \
--initrd "$WORKDIR/initrd" \
--os-release "@$SOURCE_ROOT/usr/lib/os-release" \
--uname "1.2.3-testkernel" \
--cmdline "quiet" \
--output "$WORKDIR/testuki.efi"
# 3) Build a sysinstall partition definition: a single ESP plus a root
# partition seeded from the fake source tree.
DEFS="$WORKDIR/sysinstall.d"
mkdir -p "$DEFS"
cat >"$DEFS/10-esp.conf" <<EOF
[Partition]
Type=esp
Format=vfat
SizeMinBytes=64M
SizeMaxBytes=64M
EOF
cat >"$DEFS/20-root.conf" <<EOF
[Partition]
Type=root
Format=ext4
SizeMinBytes=128M
CopyFiles=$SOURCE_ROOT:/
EOF
# 4) Allocate a sparse target file. systemd-sysinstall accepts a regular file
# path here — systemd-repart and the in-process dissect logic transparently
# handle the loop attach during install. We can't pre-attach the empty file
# via systemd-dissect --attach since that requires a valid DDI.
truncate -s 512M "$WORKDIR/target.img"
# 5) Run the installer non-interactively against the target image. Also stash a
# literal credential ('marker') so we can verify it ends up next to the UKI
# and is referenced from the boot loader entry.
CRED_VALUE="systemd-sysinstall test credential payload"
systemd-sysinstall \
--welcome=no \
--chrome=no \
--confirm=no \
--summary=no \
--erase=yes \
--variables=no \
--reboot=no \
--mute-console=no \
--copy-locale=no \
--copy-keymap=no \
--copy-timezone=no \
--set-credential="marker:$CRED_VALUE" \
--kernel="$WORKDIR/testuki.efi" \
--definitions="$DEFS" \
"$WORKDIR/target.img"
# 6) Attach the freshly installed image as a loopback device for inspection.
LOOPDEV="$(systemd-dissect --attach "$WORKDIR/target.img")"
# Verify the resulting on-disk layout. The disk must now carry a GPT with at
# least an ESP partition.
sfdisk_dump="$(sfdisk --dump "$LOOPDEV")"
assert_in "C12A7328-F81F-11D2-BA4B-00A0C93EC93B" "$sfdisk_dump"
# 7) Mount the image read-only and verify the installed artifacts: an entry
# file referencing the UKI on the ESP, the UKI itself, and the systemd-boot
# binary.
MNT="$WORKDIR/mnt"
mkdir -p "$MNT"
systemd-dissect --mount --read-only "$LOOPDEV" "$MNT"
MOUNTED=1
ESP="$MNT/efi"
test -d "$ESP/loader/entries"
# Exactly one entry should have been linked, and it should reference the UKI
# we passed via --kernel=.
ENTRY=$(find "$ESP/loader/entries" -maxdepth 1 -name '*.conf' -type f | head -n1)
test -n "$ENTRY"
grep -E "^uki /[^/]+/testuki\.efi$" "$ENTRY" >/dev/null
# The UKI file referenced in the entry must exist on the ESP.
UKI_PATH=$(awk '/^uki / { print $2 }' "$ENTRY")
test -n "$UKI_PATH"
test -f "$ESP$UKI_PATH"
# bootctl install should have placed sd-boot on the ESP.
find "$ESP/EFI/systemd" -type f -iname 'systemd-boot*.efi' | grep . >/dev/null
# The credential we passed via --set-credential= must have been encrypted and
# placed next to the UKI, and must be referenced as 'extra' from the entry.
UKI_DIR="$(dirname "$ESP$UKI_PATH")"
TOKEN_DIR="$(basename "$UKI_DIR")"
test -s "$UKI_DIR/marker.cred"
grep -E "^extra /$TOKEN_DIR/marker\.cred$" "$ENTRY" >/dev/null
# Locale/keymap/timezone propagation is off, so those .cred files must NOT
# exist on the ESP.
test ! -e "$UKI_DIR/firstboot.locale.cred"
test ! -e "$UKI_DIR/firstboot.keymap.cred"
test ! -e "$UKI_DIR/firstboot.timezone.cred"
# 8) The seeded files from the fake source tree must end up in the new root.
test -f "$MNT/usr/lib/os-release"
grep '^ID=testos$' "$MNT/usr/lib/os-release" >/dev/null
+6
View File
@@ -240,6 +240,7 @@ units = [
},
{ 'file' : 'sysinit.target' },
{ 'file' : 'syslog.socket' },
{ 'file' : 'system-install.target' },
{
'file' : 'system-systemd\\x2dcryptsetup.slice',
'conditions' : ['HAVE_LIBCRYPTSETUP'],
@@ -778,6 +779,11 @@ units = [
'file' : 'systemd-sysext@.service',
'conditions' : ['ENABLE_SYSEXT'],
},
{
'file' : 'systemd-sysinstall.service',
'conditions' : ['ENABLE_SYSINSTALL'],
'symlinks' : ['system-install.target.wants/'],
},
{
'file' : 'systemd-sysupdate-reboot.service.in',
'conditions' : ['ENABLE_SYSUPDATE'],
+15
View File
@@ -0,0 +1,15 @@
# 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=System Installer
Documentation=man:systemd-sysinstall(8)
Requires=sysinit.target
After=sysinit.target
AllowIsolate=yes
+22
View File
@@ -0,0 +1,22 @@
# 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=System Install Tool
Documentation=man:systemd-sysinstall(8)
Wants=systemd-logind.service
After=systemd-logind.service
[Service]
ExecStart=systemd-sysinstall --variables=yes --reboot=yes --mute-console=yes
StandardOutput=tty
StandardInput=tty
StandardError=tty
TTYReset=yes
FailureAction=halt