]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sysinstall: Add varlink interface
authorJulian Sparber <julian@sparber.net>
Fri, 26 Jun 2026 16:48:23 +0000 (18:48 +0200)
committerJulian Sparber <julian@sparber.net>
Fri, 3 Jul 2026 09:16:46 +0000 (11:16 +0200)
Allow graphical installers to use system sysinstall.
This will be used by gnome-setup to install GNOME OS and ideally installers
of other distributions talk to the same varlink interface.

12 files changed:
src/shared/meson.build
src/shared/varlink-io.systemd.Repart.c
src/shared/varlink-io.systemd.Repart.h
src/shared/varlink-io.systemd.SysInstall.c [new file with mode: 0644]
src/shared/varlink-io.systemd.SysInstall.h [new file with mode: 0644]
src/sysinstall/io.systemd.sysinstall.policy [new file with mode: 0644]
src/sysinstall/meson.build
src/sysinstall/sysinstall.c
units/meson.build
units/systemd-sysinstall.service
units/systemd-sysinstall.socket [new file with mode: 0644]
units/systemd-sysinstall@.service [new file with mode: 0644]

index e2bef83bd267273333e87b22d6c82476cca8880e..09563468aee5a13c9c8b73835419d8cecd96ab3b 100644 (file)
@@ -251,6 +251,7 @@ shared_sources = files(
         'varlink-io.systemd.Resolve.Monitor.c',
         'varlink-io.systemd.Shutdown.c',
         'varlink-io.systemd.StorageProvider.c',
+        'varlink-io.systemd.SysInstall.c',
         'varlink-io.systemd.SysUpdate.c',
         'varlink-io.systemd.SysUpdate.Notify.c',
         'varlink-io.systemd.Udev.c',
index fcfdcd3d650a8154f793b20fb3a9b12a6a6fdc72..968d643f017c040823e190eba9aece635065c2c6 100644 (file)
@@ -29,7 +29,7 @@ static SD_VARLINK_DEFINE_ENUM_TYPE(
                 SD_VARLINK_FIELD_COMMENT("Always create a new partition table, potentially overwriting an existing table"),
                 SD_VARLINK_DEFINE_ENUM_VALUE(force));
 
-static SD_VARLINK_DEFINE_ENUM_TYPE(
+SD_VARLINK_DEFINE_ENUM_TYPE(
                 BlockDeviceAction,
                 SD_VARLINK_FIELD_COMMENT("The device is currently present and a candidate. Emitted both during the initial enumeration and for live uevents (any action other than 'remove' is propagated as 'add', including the synthesized transitions when a device becomes empty or read-only and a relevant ignore* input is in effect)."),
                 SD_VARLINK_DEFINE_ENUM_VALUE(add),
@@ -93,9 +93,9 @@ static SD_VARLINK_DEFINE_METHOD_FULL(
                 SD_VARLINK_DEFINE_OUTPUT(subsystem, SD_VARLINK_STRING, SD_VARLINK_NULLABLE));
 
 
-static SD_VARLINK_DEFINE_ERROR(NoCandidateDevices);
-static SD_VARLINK_DEFINE_ERROR(ConflictingDiskLabelPresent);
-static SD_VARLINK_DEFINE_ERROR(
+SD_VARLINK_DEFINE_ERROR(NoCandidateDevices);
+SD_VARLINK_DEFINE_ERROR(ConflictingDiskLabelPresent);
+SD_VARLINK_DEFINE_ERROR(
                 InsufficientFreeSpace,
                 SD_VARLINK_FIELD_COMMENT("Minimal size of the disk required for the installation."),
                 SD_VARLINK_DEFINE_FIELD(minimalSizeBytes, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
@@ -103,7 +103,7 @@ static SD_VARLINK_DEFINE_ERROR(
                 SD_VARLINK_DEFINE_FIELD(needFreeBytes, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
                 SD_VARLINK_FIELD_COMMENT("Size of the selected block device."),
                 SD_VARLINK_DEFINE_FIELD(currentSizeBytes, SD_VARLINK_INT, SD_VARLINK_NULLABLE));
-static SD_VARLINK_DEFINE_ERROR(
+SD_VARLINK_DEFINE_ERROR(
                 DiskTooSmall,
                 SD_VARLINK_FIELD_COMMENT("Minimal size of the disk required for the installation."),
                 SD_VARLINK_DEFINE_FIELD(minimalSizeBytes, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
index 7c7cb7dd79f86e7550b40c3a1ba8d1fc2ff190a3..536ea88ccdd98ecd8f73ee0a4a911507b558172d 100644 (file)
@@ -4,3 +4,9 @@
 #include "sd-varlink-idl.h"
 
 extern const sd_varlink_interface vl_interface_io_systemd_Repart;
+extern const sd_varlink_symbol vl_type_BlockDeviceAction;
+
+extern const sd_varlink_symbol vl_error_NoCandidateDevices;
+extern const sd_varlink_symbol vl_error_ConflictingDiskLabelPresent;
+extern const sd_varlink_symbol vl_error_InsufficientFreeSpace;
+extern const sd_varlink_symbol vl_error_DiskTooSmall;
diff --git a/src/shared/varlink-io.systemd.SysInstall.c b/src/shared/varlink-io.systemd.SysInstall.c
new file mode 100644 (file)
index 0000000..c93a027
--- /dev/null
@@ -0,0 +1,125 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "sd-varlink-idl.h"
+
+#include "varlink-io.systemd.SysInstall.h"
+#include "varlink-io.systemd.Repart.h"
+
+static SD_VARLINK_DEFINE_ENUM_TYPE(
+                ProgressPhase,
+
+                SD_VARLINK_DEFINE_ENUM_VALUE(encrypt_credentials),
+                SD_VARLINK_DEFINE_ENUM_VALUE(install_partitions),
+                SD_VARLINK_DEFINE_ENUM_VALUE(mount_partitions),
+                SD_VARLINK_DEFINE_ENUM_VALUE(install_kernel),
+                SD_VARLINK_DEFINE_ENUM_VALUE(install_bootloader),
+                SD_VARLINK_DEFINE_ENUM_VALUE(unmount_partitions));
+
+static SD_VARLINK_DEFINE_ENUM_TYPE(
+                DeviceFit,
+
+                SD_VARLINK_DEFINE_ENUM_VALUE(enough_free_space),
+                SD_VARLINK_DEFINE_ENUM_VALUE(insufficent_free_space),
+                SD_VARLINK_DEFINE_ENUM_VALUE(disk_too_small),
+                SD_VARLINK_DEFINE_ENUM_VALUE(conflicting_disk_label_present));
+
+static SD_VARLINK_DEFINE_STRUCT_TYPE(
+                Credential,
+                SD_VARLINK_FIELD_COMMENT("The id of the credential."),
+                SD_VARLINK_DEFINE_FIELD(id, SD_VARLINK_STRING, 0),
+                SD_VARLINK_FIELD_COMMENT("The value of the credential in base64 encoding."),
+                SD_VARLINK_DEFINE_FIELD(value, SD_VARLINK_STRING, 0));
+
+static SD_VARLINK_DEFINE_METHOD_FULL(
+                Run,
+                SD_VARLINK_SUPPORTS_MORE,
+                SD_VARLINK_FIELD_COMMENT("Full path to the block device node to operate on."),
+                SD_VARLINK_DEFINE_INPUT(node, SD_VARLINK_STRING, 0),
+                SD_VARLINK_FIELD_COMMENT("Path to directory containing definition files."),
+                SD_VARLINK_DEFINE_INPUT(definitions, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE),
+                SD_VARLINK_FIELD_COMMENT("If true, fully erase the target block device."),
+                SD_VARLINK_DEFINE_INPUT(erase, SD_VARLINK_BOOL, 0),
+                SD_VARLINK_FIELD_COMMENT("If true, EFI variables are modified to register the installed boot loader in the firmware's boot options database."),
+                SD_VARLINK_DEFINE_INPUT(variables, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
+                SD_VARLINK_FIELD_COMMENT("The path to a kernel image, if missing the current kernel is dedected and used."),
+                SD_VARLINK_DEFINE_INPUT(kernelImagePath, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
+
+                SD_VARLINK_FIELD_COMMENT("If true, the current locale is copied to target system"),
+                SD_VARLINK_DEFINE_INPUT(copyLocale, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
+                SD_VARLINK_FIELD_COMMENT("If true, the current keymap is copied to target system"),
+                SD_VARLINK_DEFINE_INPUT(copyKeymap, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
+                SD_VARLINK_FIELD_COMMENT("If true, the current timezone is copied to target system"),
+                SD_VARLINK_DEFINE_INPUT(copyTimezone, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
+
+                SD_VARLINK_FIELD_COMMENT("A list of credentials to be installed to target system."),
+                SD_VARLINK_DEFINE_INPUT_BY_TYPE(credentials, Credential, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE),
+
+                SD_VARLINK_FIELD_COMMENT("If used with the 'more' flag, a phase identifier is sent in progress updates."),
+                SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(phase, ProgressPhase, SD_VARLINK_NULLABLE),
+                SD_VARLINK_FIELD_COMMENT("If used with the 'more' flag, an object identifier string is sent in progress updates."),
+                SD_VARLINK_DEFINE_OUTPUT(object, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
+                SD_VARLINK_FIELD_COMMENT("If used with the 'more' flag, a progress percentage (specific to the work done for the specified phase+object is sent in progress updates)."),
+                SD_VARLINK_DEFINE_OUTPUT(progress, SD_VARLINK_INT, SD_VARLINK_NULLABLE));
+
+static SD_VARLINK_DEFINE_METHOD_FULL(
+                ListCandidateDevices,
+                SD_VARLINK_REQUIRES_MORE,
+                SD_VARLINK_FIELD_COMMENT("Path to directory containing definition files, used to evaluate the fit of the target OS for each block device."),
+                SD_VARLINK_DEFINE_INPUT(definitions, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE),
+                SD_VARLINK_FIELD_COMMENT("If true, keep the call open after the initial enumeration and stream live add/remove notifications as block-subsystem uevents arrive. The end of the initial enumeration is marked by exactly one notification with action='ready' and no other fields. Defaults to false."),
+                SD_VARLINK_DEFINE_INPUT(subscribe, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
+                SD_VARLINK_FIELD_COMMENT("Discriminator field. Only set in subscribe mode. 'add' carries the full device record, 'remove' carries only node, 'ready' carries no other fields and is sent once after the initial enumeration."),
+                SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(action, BlockDeviceAction, SD_VARLINK_NULLABLE),
+                SD_VARLINK_FIELD_COMMENT("The device node path of the block device."),
+                SD_VARLINK_DEFINE_OUTPUT(node, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
+                SD_VARLINK_FIELD_COMMENT("List of symlinks pointing to the device node, if any."),
+                SD_VARLINK_DEFINE_OUTPUT(symlinks, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE),
+                SD_VARLINK_FIELD_COMMENT("The Linux kernel disk sequence number identifying the medium."),
+                SD_VARLINK_DEFINE_OUTPUT(diskseq, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
+                SD_VARLINK_FIELD_COMMENT("The size of the block device in bytes."),
+                SD_VARLINK_DEFINE_OUTPUT(sizeBytes, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
+                SD_VARLINK_FIELD_COMMENT("The device vendor string if known."),
+                SD_VARLINK_DEFINE_OUTPUT(vendor, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
+                SD_VARLINK_FIELD_COMMENT("The device model string if known."),
+                SD_VARLINK_DEFINE_OUTPUT(model, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
+                SD_VARLINK_FIELD_COMMENT("The subsystem the block device belongs to if known."),
+                SD_VARLINK_DEFINE_OUTPUT(subsystem, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
+                SD_VARLINK_FIELD_COMMENT("The fit indicating whether the OS would have enough available space on the device."),
+                SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(fit, DeviceFit, SD_VARLINK_NULLABLE),
+                SD_VARLINK_FIELD_COMMENT("Minimal size of the disk required for the installation."),
+                SD_VARLINK_DEFINE_OUTPUT(minimalSizeBytes, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
+                SD_VARLINK_FIELD_COMMENT("Additional free space needed for the installation."),
+                SD_VARLINK_DEFINE_OUTPUT(needFreeBytes, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
+                SD_VARLINK_FIELD_COMMENT("Current allocated size of this block device."),
+                SD_VARLINK_DEFINE_OUTPUT(currentSizeBytes, SD_VARLINK_INT, SD_VARLINK_NULLABLE));
+
+SD_VARLINK_DEFINE_INTERFACE(
+                io_systemd_SysInstall,
+                "io.systemd.SysInstall",
+                SD_VARLINK_INTERFACE_COMMENT("API for installing the OS to another block device."),
+
+                SD_VARLINK_SYMBOL_COMMENT("Progress phase identifiers. Note that we might add more phases here, and thus identifiers. Frontends can choose to display the phase to the user in some human readable form, or not do that, but if they do it and they receive a notification for a so far unknown phase, they should just ignore it."),
+                &vl_type_ProgressPhase,
+
+                SD_VARLINK_SYMBOL_COMMENT("Description about the fit of an OS on a block device."),
+                &vl_type_DeviceFit,
+
+                SD_VARLINK_SYMBOL_COMMENT("Discriminator on streamed ListCandidateDevices replies in subscribe mode."),
+                &vl_type_BlockDeviceAction,
+
+                SD_VARLINK_SYMBOL_COMMENT("A credential to install to target OS."),
+                &vl_type_Credential,
+
+                SD_VARLINK_SYMBOL_COMMENT("Invoke the actual installation of the OS. If invoked with 'more' enabled will report progress, otherwise will just report completion."),
+                &vl_method_Run,
+                SD_VARLINK_SYMBOL_COMMENT("An incompatible disk label present, and not told to erase it."),
+                &vl_error_ConflictingDiskLabelPresent,
+                SD_VARLINK_SYMBOL_COMMENT("The target disk has insufficient free space to fit all requested partitions. (But the disk would fit, if emptied.)"),
+                &vl_error_InsufficientFreeSpace,
+                SD_VARLINK_SYMBOL_COMMENT("The target disk is too small to fit the installation. (Regardless if emptied or not.)"),
+                &vl_error_DiskTooSmall,
+
+                SD_VARLINK_SYMBOL_COMMENT("Return a list of candidate block devices, i.e. that support partition scanning and other requirements for successful operation."),
+                &vl_method_ListCandidateDevices,
+                SD_VARLINK_SYMBOL_COMMENT("Not a single candidate block device could be found."),
+                &vl_error_NoCandidateDevices);
diff --git a/src/shared/varlink-io.systemd.SysInstall.h b/src/shared/varlink-io.systemd.SysInstall.h
new file mode 100644 (file)
index 0000000..d412e07
--- /dev/null
@@ -0,0 +1,6 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-varlink-idl.h"
+
+extern const sd_varlink_interface vl_interface_io_systemd_SysInstall;
diff --git a/src/sysinstall/io.systemd.sysinstall.policy b/src/sysinstall/io.systemd.sysinstall.policy
new file mode 100644 (file)
index 0000000..d6bc796
--- /dev/null
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?> <!--*-nxml-*-->
+<!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
+        "https://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
+
+<!--
+  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.
+-->
+
+<policyconfig>
+
+        <vendor>The systemd Project</vendor>
+        <vendor_url>https://systemd.io</vendor_url>
+
+        <action id="io.systemd.sysinstall.Run">
+                <description gettext-domain="systemd">Install OS to disk</description>
+                <message gettext-domain="systemd">Authentication is required to install an OS to disk.</message>
+                <defaults>
+                        <allow_any>auth_admin</allow_any>
+                        <allow_inactive>auth_admin</allow_inactive>
+                        <allow_active>auth_admin_keep</allow_active>
+                </defaults>
+        </action>
+
+        <action id="io.systemd.sysinstall.ListCandidateDevices">
+                <description gettext-domain="systemd">List candidate devices</description>
+                <message gettext-domain="systemd">Authentication is required to list device candidates.</message>
+                <defaults>
+                        <allow_any>auth_admin</allow_any>
+                        <allow_inactive>auth_admin</allow_inactive>
+                        <allow_active>auth_admin_keep</allow_active>
+                </defaults>
+        </action>
+</policyconfig>
index 1d8be6036564b94e8b04896d1aa12b207cac7b9a..79618d69338a598bbf692bfbb76b0b09e3d8ead8 100644 (file)
@@ -8,3 +8,8 @@ executables += [
                 'sources' : files('sysinstall.c'),
         },
 ]
+
+if conf.get('ENABLE_SYSINSTALL') == 1
+        install_data('io.systemd.sysinstall.policy',
+                     install_dir : polkitpolicydir)
+endif
index a9aee0535f92571a6ca42238a59c92b60a82bdcb..7560e34a0b52ac7767f414be28ab9f06984cb9be 100644 (file)
@@ -11,6 +11,7 @@
 #include "blockdev-list.h"
 #include "build.h"
 #include "build-path.h"
+#include "bus-polkit.h"
 #include "chase.h"
 #include "conf-files.h"
 #include "constants.h"
@@ -24,6 +25,7 @@
 #include "format-util.h"
 #include "fs-util.h"
 #include "glyph-util.h"
+#include "hashmap.h"
 #include "help-util.h"
 #include "image-policy.h"
 #include "json-util.h"
 #include "parse-util.h"
 #include "path-util.h"
 #include "prompt-util.h"
+#include "string-table.h"
 #include "strv.h"
 #include "terminal-util.h"
+#include "varlink-io.systemd.SysInstall.h"
 #include "varlink-util.h"
 
 static char *arg_node = NULL;
@@ -58,12 +62,98 @@ static bool arg_copy_keymap = true;
 static bool arg_copy_timezone = true;
 static bool arg_chrome = true;
 static bool arg_mute_console = false;
+static bool arg_varlink = false;
 
 STATIC_DESTRUCTOR_REGISTER(arg_node, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_definitions, strv_freep);
 STATIC_DESTRUCTOR_REGISTER(arg_kernel_image, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_credentials, machine_credential_context_done);
 
+typedef enum ProgressPhase {
+        PROGRESS_ENCRYPT_CREDENTIALS,
+        PROGRESS_INSTALL_PARTITIONS,
+        PROGRESS_MOUNT_PARTITIONS,
+        PROGRESS_INSTALL_KERNEL,
+        PROGRESS_INSTALL_BOOTLOADER,
+        PROGRESS_UNMOUNT_PARTITIONS,
+        _PROGRESS_PHASE_MAX,
+        _PROGRESS_PHASE_INVALID = -EINVAL,
+} ProgressPhase;
+
+static const char *progress_phase_table[_PROGRESS_PHASE_MAX] = {
+        [PROGRESS_ENCRYPT_CREDENTIALS] = "encrypt-credentials",
+        [PROGRESS_INSTALL_PARTITIONS]  = "install-partitions",
+        [PROGRESS_MOUNT_PARTITIONS]    = "mount-partitions",
+        [PROGRESS_INSTALL_KERNEL]      = "install-kernel",
+        [PROGRESS_INSTALL_BOOTLOADER]  = "install-bootloader",
+        [PROGRESS_UNMOUNT_PARTITIONS]  = "unmount-partitions",
+};
+
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(progress_phase, ProgressPhase);
+
+static const char *progress_phase_log_table[_PROGRESS_PHASE_MAX] = {
+        [PROGRESS_ENCRYPT_CREDENTIALS] = "Encrypting credentials...",
+        [PROGRESS_INSTALL_PARTITIONS]  = "Installing partitions...",
+        [PROGRESS_MOUNT_PARTITIONS]    = "Mounting partitions...",
+        [PROGRESS_INSTALL_KERNEL]      = "Installing kernel...",
+        [PROGRESS_INSTALL_BOOTLOADER]  = "Installing boot loader...",
+        [PROGRESS_UNMOUNT_PARTITIONS]  = "Unmounting partitions...",
+};
+
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(progress_phase_log, ProgressPhase);
+
+typedef enum DeviceFit {
+        DEVICE_FIT_ENOUGH_FREE_SPACE,
+        DEVICE_FIT_INSUFFICIENT_FREE_SPACE,
+        DEVICE_FIT_DISK_TOO_SMALL,
+        DEVICE_FIT_CONFLICTING_DISK_LABEL_PRESENT,
+        _DEVICE_FIT_MAX,
+        _DEVICE_FIT_INVALID = -EINVAL,
+} DeviceFit;
+
+static const char *device_fit_table[_DEVICE_FIT_MAX] = {
+        [DEVICE_FIT_ENOUGH_FREE_SPACE]              = "enough-free-space",
+        [DEVICE_FIT_INSUFFICIENT_FREE_SPACE]        = "insufficient-free-space",
+        [DEVICE_FIT_DISK_TOO_SMALL]                 = "disk-too-small",
+        [DEVICE_FIT_CONFLICTING_DISK_LABEL_PRESENT] = "conflicting-disk-label-present",
+};
+
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(device_fit, DeviceFit);
+
+typedef struct SysInstallContext {
+        bool copy_locale;
+        bool copy_keymap;
+        bool copy_timezone;
+        MachineCredentialContext credentials;
+        char **definitions;
+        bool erase;
+        char *node;
+        char *kernel_filename;
+        int kernel_fd;
+        bool touch_variables;
+
+        sd_varlink *repart_link;
+
+        sd_varlink *link; /* If 'more' is used on the Varlink call, we'll send progress info over this link */
+} SysInstallContext;
+
+static void sysinstall_context_done(SysInstallContext *c) {
+        assert(c);
+
+        strv_free(c->definitions);
+
+        free(c->node);
+
+        free(c->kernel_filename);
+        safe_close(c->kernel_fd);
+
+        machine_credential_context_done(&c->credentials);
+
+        sd_varlink_flush_close_unref(c->repart_link);
+
+        sd_varlink_unref(c->link);
+}
+
 static int help(void) {
         int r;
 
@@ -210,6 +300,12 @@ static int parse_argv(int argc, char *argv[]) {
                         return log_oom();
         }
 
+        r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT);
+        if (r < 0)
+                return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m");
+        if (r > 0)
+                arg_varlink = true;
+
         return 1;
 }
 
@@ -403,6 +499,195 @@ static int prompt_block_device(sd_varlink **repart_link, char **ret_node) {
         return 0;
 }
 
+static int sysinstall_context_notify(
+                SysInstallContext *context,
+                ProgressPhase phase,
+                const char *object,
+                unsigned percent) {
+
+        int r;
+
+        assert(context);
+        assert(phase >= 0);
+        assert(phase < _PROGRESS_PHASE_MAX);
+
+        log_notice("%s%s%s",
+                   emoji_enabled() ? phase == PROGRESS_ENCRYPT_CREDENTIALS ?  glyph(GLYPH_LOCK_AND_KEY) : glyph(GLYPH_COMPUTER_DISK) : "",
+                   emoji_enabled() ? " " : "",
+                   progress_phase_log_to_string(phase));
+
+        if (context->link) {
+                r = sd_varlink_notifybo(
+                                context->link,
+                                JSON_BUILD_PAIR_ENUM("phase", progress_phase_to_string(phase)),
+                                JSON_BUILD_PAIR_STRING_NON_EMPTY("object", object),
+                                JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("progress", percent, UINT_MAX));
+                if (r < 0)
+                        log_debug_errno(r, "Failed to send varlink notify progress notification, ignoring: %m");
+
+                r = sd_varlink_flush(context->link);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to flush varlink notify progress notification, ignoring: %m");
+        }
+
+        return 0;
+}
+
+typedef struct RepartResult {
+        int ret;
+        SysInstallContext *context;
+} RepartResult;
+
+static int handle_repart_reply(
+                sd_varlink *link,
+                sd_json_variant *reply,
+                const char *error_id,
+                sd_varlink_reply_flags_t flags,
+                void *userdata) {
+
+        RepartResult *result = userdata;
+        int r;
+
+        assert(result);
+
+        struct {
+                uint64_t min_size;
+                uint64_t current_size;
+                uint64_t need_free;
+
+                const char *phase;
+                const char *object;
+                unsigned progress;
+        } p = {
+                .min_size = UINT64_MAX,
+                .current_size = UINT64_MAX,
+                .need_free = UINT64_MAX,
+                .progress = UINT_MAX,
+        };
+
+        static const sd_json_dispatch_field dispatch_table[] = {
+                { "minimalSizeBytes", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,       voffsetof(p, min_size),     0 },
+                { "currentSizeBytes", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,       voffsetof(p, current_size), 0 },
+                { "needFreeBytes",    _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,       voffsetof(p, need_free),    0 },
+                { "phase",            _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_const_string, voffsetof(p, phase),        0 },
+                { "object",           _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_const_string, voffsetof(p, object),       0 },
+                { "progress",         _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint,         voffsetof(p, progress),     0 },
+                {}
+        };
+
+        r = sd_json_dispatch(reply, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p);
+        if (r < 0)
+                return result->ret = r;
+
+        if (error_id) {
+                const char *sysinstall_error_id = NULL;
+
+                if (streq(error_id, "io.systemd.Repart.InsufficientFreeSpace")) {
+                        sysinstall_error_id = "io.systemd.SysInstall.InsufficientFreeSpace";
+                        result->ret = log_error_errno(SYNTHETIC_ERRNO(ENOSPC), "Not enough free space on disk, cannot install.");
+                } else if (streq(error_id, "io.systemd.Repart.DiskTooSmall")) {
+                        sysinstall_error_id = "io.systemd.SysInstall.DiskTooSmall";
+
+                        result->ret = log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Disk too small for installation, cannot install.");
+                } else if (streq(error_id, "io.systemd.Repart.ConflictingDiskLabelPresent")) {
+                        sysinstall_error_id = "io.systemd.SysInstall.ConflictingDiskLabelPresent";
+
+                        result->ret = log_error_errno(
+                                        SYNTHETIC_ERRNO(EHWPOISON),
+                                        "A conflicting disk label is already present on the target disk, cannot install unless disk is erased.");
+                }
+
+                if (sysinstall_error_id && result->context->link) {
+                        r = sd_varlink_errorbo(
+                                        result->context->link,
+                                        sysinstall_error_id,
+                                        JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("currentSizeBytes", p.current_size, UINT64_MAX),
+                                        JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("needFreeBytes", p.need_free, UINT64_MAX),
+                                        JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("minimalSizeBytes", p.min_size, UINT64_MAX));
+
+                        if (r < 0)
+                                return result->ret = r;
+
+                        return result->ret;
+                }
+
+                r = sd_varlink_error_to_errno(error_id, reply); /* If this is a system errno style error, output it with %m */
+                if (r != -EBADR)
+                        return result->ret = log_error_errno(r, "Failed to issue io.systemd.Repart.Run() varlink call: %m");
+
+                return result->ret = log_error_errno(r, "Failed to issue io.systemd.Repart.Run() varlink call: %s", error_id);
+        }
+
+        if ((p.progress != UINT_MAX || p.object) && result->context->link)
+                (void) sysinstall_context_notify(result->context, PROGRESS_INSTALL_PARTITIONS, p.object, p.progress);
+
+        return result->ret = 0;
+}
+
+static int sysinstall_context_invoke_repart_run(SysInstallContext *context) {
+
+        int r;
+
+        assert(context);
+
+        r = connect_to_repart(&context->repart_link);
+        if (r < 0)
+                return r;
+
+        /* Seeding the partitions might be very slow, disable timeout */
+        r = sd_varlink_set_relative_timeout(context->repart_link, UINT64_MAX);
+        if (r < 0)
+                return log_error_errno(r, "Failed to disable IPC timeout: %m");
+
+        RepartResult result = {
+                .context = context,
+        };
+
+        sd_varlink_set_userdata(context->repart_link, &result);
+
+        r = sd_varlink_bind_reply(context->repart_link, handle_repart_reply);
+        if (r < 0)
+                return log_error_errno(r, "Failed to bind repart reply callback: %m");
+
+        r = sd_varlink_observebo(
+                        context->repart_link,
+                        "io.systemd.Repart.Run",
+                        SD_JSON_BUILD_PAIR_STRING("node", context->node),
+                        SD_JSON_BUILD_PAIR_STRING("empty", context->erase ? "force" : "allow"),
+                        SD_JSON_BUILD_PAIR_BOOLEAN("dryRun", false),
+                        SD_JSON_BUILD_PAIR_CONDITION(!!context->definitions, "definitions", SD_JSON_BUILD_STRV(context->definitions)),
+                        SD_JSON_BUILD_PAIR_BOOLEAN("deferPartitionsEmpty", true),
+                        SD_JSON_BUILD_PAIR_BOOLEAN("deferPartitionsFactoryReset", true));
+        if (r < 0)
+                return log_error_errno(r, "Failed to issue io.systemd.Repart.Run() varlink call: %m");
+
+        for (;;) {
+                r = sd_varlink_is_idle(context->repart_link);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to check if varlink connection is idle: %m");
+                if (r > 0)
+                        break;
+
+                r = sd_varlink_process(context->repart_link);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to process varlink connection: %m");
+                if (r != 0)
+                        continue;
+
+                r = sd_varlink_wait(context->repart_link, USEC_INFINITY);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to wait for varlink connection events: %m");
+        }
+
+        sd_varlink_set_userdata(context->repart_link, NULL);
+
+        r = sd_varlink_bind_reply(context->repart_link, NULL);
+        if (r < 0)
+                return log_error_errno(r, "Failed to unbind repart reply callback: %m");
+
+        return result.ret;
+}
+
 static int read_space_metrics(
                 sd_json_variant *v,
                 uint64_t *min_size,
@@ -447,6 +732,7 @@ static int invoke_repart(
                 const char *node,
                 bool erase,
                 bool dry_run,
+                char **definitions,
                 uint64_t *min_size,        /* initialized both on success and error */
                 uint64_t *current_size,    /* ditto */
                 uint64_t *need_free) {     /* ditto */
@@ -481,7 +767,7 @@ static int invoke_repart(
                         SD_JSON_BUILD_PAIR_STRING("node", node),
                         SD_JSON_BUILD_PAIR_STRING("empty", erase ? "force" : "allow"),
                         SD_JSON_BUILD_PAIR_BOOLEAN("dryRun", dry_run),
-                        SD_JSON_BUILD_PAIR_CONDITION(!!arg_definitions, "definitions", SD_JSON_BUILD_STRV(arg_definitions)),
+                        SD_JSON_BUILD_PAIR_CONDITION(!!definitions, "definitions", SD_JSON_BUILD_STRV(definitions)),
                         SD_JSON_BUILD_PAIR_BOOLEAN("deferPartitionsEmpty", true),
                         SD_JSON_BUILD_PAIR_BOOLEAN("deferPartitionsFactoryReset", true));
         if (r < 0) {
@@ -656,6 +942,7 @@ static int validate_run(sd_varlink **repart_link, const char *node) {
                                 node,
                                 try_erase,
                                 /* dry_run= */ true,
+                                arg_definitions,
                                 &min_size,
                                 &current_size,
                                 &need_free);
@@ -705,12 +992,9 @@ static int validate_run(sd_varlink **repart_link, const char *node) {
         }
 }
 
-static int show_summary(void) {
+static int sysinstall_context_show_summary(SysInstallContext *context) {
         int r;
 
-        if (!arg_summary)
-                return 0;
-
         printf("\n"
                "%sSummary:%s\n", ansi_underline(), ansi_normal());
 
@@ -721,12 +1005,12 @@ static int show_summary(void) {
         r = table_add_many(
                         table,
                         TABLE_FIELD, "Selected Disk",
-                        TABLE_STRING, arg_node,
+                        TABLE_STRING, context->node,
                         TABLE_FIELD, "Erase Disk",
-                        TABLE_BOOLEAN, arg_erase,
-                        TABLE_SET_COLOR, arg_erase ? ansi_highlight_red() : NULL,
+                        TABLE_BOOLEAN, context->erase,
+                        TABLE_SET_COLOR, context->erase ? ansi_highlight_red() : NULL,
                         TABLE_FIELD, "Register in Firmware",
-                        TABLE_BOOLEAN, arg_touch_variables);
+                        TABLE_BOOLEAN, context->touch_variables);
         if (r < 0)
                 return table_log_add_error(r);
 
@@ -739,7 +1023,7 @@ static int show_summary(void) {
         };
 
         STRV_FOREACH_PAIR(id, text, map) {
-                MachineCredential *c = machine_credential_find(&arg_credentials, *id);
+                MachineCredential *c = machine_credential_find(&context->credentials, *id);
                 if (!c)
                         continue;
 
@@ -756,7 +1040,7 @@ static int show_summary(void) {
         }
 
         unsigned n_extra_credentials = 0;
-        FOREACH_ARRAY(cred, arg_credentials.credentials, arg_credentials.n_credentials) {
+        FOREACH_ARRAY(cred, context->credentials.credentials, context->credentials.n_credentials) {
                 bool covered = false;
 
                 STRV_FOREACH_PAIR(id, text, map)
@@ -894,6 +1178,7 @@ static int connect_to_bootctl(sd_varlink **link) {
 
 static int invoke_bootctl_install(
                 sd_varlink **link,
+                bool variables,
                 const char *root_dir,
                 int root_fd) {
         int r;
@@ -919,7 +1204,7 @@ static int invoke_bootctl_install(
                         SD_JSON_BUILD_PAIR_STRING("operation", "new"),
                         SD_JSON_BUILD_PAIR_INTEGER("rootFileDescriptor", fd_idx),
                         SD_JSON_BUILD_PAIR_STRING("rootDirectory", root_dir),
-                        SD_JSON_BUILD_PAIR_BOOLEAN("touchVariables", arg_touch_variables));
+                        SD_JSON_BUILD_PAIR_BOOLEAN("touchVariables", variables));
         if (r < 0)
                 return r;
 
@@ -1017,14 +1302,11 @@ static int maybe_reboot(void) {
         return 0;
 }
 
-static int read_credential_locale(void) {
+static int read_credential_locale(MachineCredentialContext *credentials) {
         int r;
 
-        if (!arg_copy_locale)
-                return 0;
-
-        if (machine_credential_find(&arg_credentials, "firstboot.locale") ||
-            machine_credential_find(&arg_credentials, "firstboot.locale-messages"))
+        if (machine_credential_find(credentials, "firstboot.locale") ||
+            machine_credential_find(credentials, "firstboot.locale-messages"))
                 return 0;
 
         /* For the main locale we check the two env vars, and if neither is there, we use LC_NUMERIC, since
@@ -1032,14 +1314,14 @@ static int read_credential_locale(void) {
          * separate setting after all */
         const char *l = getenv("LC_ALL") ?: getenv("LANG") ?: setlocale(LC_NUMERIC, NULL);
         if (l) {
-                r = machine_credential_add(&arg_credentials, "firstboot.locale", l, /* size= */ SIZE_MAX);
+                r = machine_credential_add(credentials, "firstboot.locale", l, /* size= */ SIZE_MAX);
                 if (r < 0)
                         return log_oom();
         }
 
         const char *m = setlocale(LC_MESSAGES, NULL);
         if (m && !streq_ptr(m, l)) {
-                r = machine_credential_add(&arg_credentials, "firstboot.locale-messages", m, /* size= */ SIZE_MAX);
+                r = machine_credential_add(credentials, "firstboot.locale-messages", m, /* size= */ SIZE_MAX);
                 if (r < 0)
                         return log_oom();
         }
@@ -1047,13 +1329,10 @@ static int read_credential_locale(void) {
         return 0;
 }
 
-static int read_credential_keymap(void) {
+static int read_credential_keymap(MachineCredentialContext *credentials) {
         int r;
 
-        if (!arg_copy_keymap)
-                return 0;
-
-        if (machine_credential_find(&arg_credentials, "firstboot.keymap"))
+        if (machine_credential_find(credentials, "firstboot.keymap"))
                 return 0;
 
         _cleanup_free_ char *keymap = NULL;
@@ -1065,7 +1344,7 @@ static int read_credential_keymap(void) {
                 return log_error_errno(r, "Failed to parse '%s': %m", etc_vconsole_conf());
 
         if (!isempty(keymap)) {
-                r = machine_credential_add(&arg_credentials, "firstboot.keymap", keymap, /* size= */ SIZE_MAX);
+                r = machine_credential_add(credentials, "firstboot.keymap", keymap, /* size= */ SIZE_MAX);
                 if (r < 0)
                         return log_oom();
         }
@@ -1073,13 +1352,10 @@ static int read_credential_keymap(void) {
         return 0;
 }
 
-static int read_credential_timezone(void) {
+static int read_credential_timezone(MachineCredentialContext *credentials) {
         int r;
 
-        if (!arg_copy_timezone)
-                return 0;
-
-        if (machine_credential_find(&arg_credentials, "firstboot.timezone"))
+        if (machine_credential_find(credentials, "firstboot.timezone"))
                 return 0;
 
         _cleanup_free_ char *tz = NULL;
@@ -1087,7 +1363,7 @@ static int read_credential_timezone(void) {
         if (r < 0)
                 log_warning_errno(r, "Failed to read timezone, skipping timezone propagation: %m");
         else {
-                r = machine_credential_add(&arg_credentials, "firstboot.timezone", tz, /* size= */ SIZE_MAX);
+                r = machine_credential_add(credentials, "firstboot.timezone", tz, /* size= */ SIZE_MAX);
                 if (r < 0)
                         return log_oom();
         }
@@ -1095,20 +1371,26 @@ static int read_credential_timezone(void) {
         return 0;
 }
 
-static int read_credentials(void) {
+static int sysinstall_context_read_credentials(SysInstallContext *context) {
         int r;
 
-        r = read_credential_locale();
-        if (r < 0)
-                return r;
+        if (context->copy_locale) {
+                r = read_credential_locale(&context->credentials);
+                if (r < 0)
+                        return r;
+        }
 
-        r = read_credential_keymap();
-        if (r < 0)
-                return r;
+        if (context->copy_keymap) {
+                r = read_credential_keymap(&context->credentials);
+                if (r < 0)
+                        return r;
+        }
 
-        r = read_credential_timezone();
-        if (r < 0)
-                return r;
+        if (context->copy_timezone) {
+                r = read_credential_timezone(&context->credentials);
+                if (r < 0)
+                        return r;
+        }
 
         return 0;
 }
@@ -1179,13 +1461,13 @@ static int encrypt_one_credential(sd_varlink **link, const MachineCredential *in
         return 0;
 }
 
-static int encrypt_credentials(sd_varlink **link, char ***encrypted) {
+static int encrypt_credentials(sd_varlink **link, MachineCredentialContext *credentials, char ***encrypted) {
         int r;
 
         assert(link);
         assert(encrypted);
 
-        FOREACH_ARRAY(cred, arg_credentials.credentials, arg_credentials.n_credentials) {
+        FOREACH_ARRAY(cred, credentials->credentials, credentials->n_credentials) {
                 r = encrypt_one_credential(link, cred, encrypted);
                 if (r < 0)
                         return r;
@@ -1206,11 +1488,22 @@ static const ImagePolicy image_policy = {
         .default_flags = PARTITION_POLICY_IGNORE,
 };
 
-static int settle_definitions(void) {
+static int settle_definitions(char **definitions, char ***ret_definitions) {
+
+        _cleanup_strv_free_ char **d = NULL;
         int r;
 
-        if (arg_definitions)
+        assert(ret_definitions);
+
+        if (definitions) {
+                d = strv_copy(definitions);
+                if (!d)
+                        return log_oom();
+
+                *ret_definitions = TAKE_PTR(d);
+
                 return 0;
+        }
 
         /* If /usr/lib/repart.sysinstall.d/ is populated, use it, otherwise use the regular definition
          * files */
@@ -1226,11 +1519,549 @@ static int settle_definitions(void) {
                 return log_error_errno(r, "Failed to enumerate *.conf files: %m");
 
         if (!strv_isempty(files)) {
-                arg_definitions = strv_copy(CONF_PATHS_STRV("repart.sysinstall.d"));
-                if (!arg_definitions)
+                d = strv_copy(CONF_PATHS_STRV("repart.sysinstall.d"));
+                if (!d)
                         return log_oom();
+
+                *ret_definitions = TAKE_PTR(d);
+        }
+
+        return 0;
+}
+
+static int sysinstall_context_settle_definitions(SysInstallContext *context,
+                                                 char **definitions) {
+
+        return settle_definitions(definitions, &context->definitions);
+}
+
+static int sysinstall_context_settle_kernel_image(SysInstallContext *context,
+                                                  const char *kernel_image) {
+
+        _cleanup_free_ char *kernel_filename = NULL;
+        _cleanup_close_ int kernel_fd = -EBADF;
+        int r;
+
+        assert(context->kernel_fd < 0);
+        assert(!context->kernel_filename);
+
+        if (kernel_image) {
+                r = path_extract_filename(kernel_image, &kernel_filename);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to extract filename from kernel path '%s': %m", kernel_image);
+                if (r == O_DIRECTORY)
+                        return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Kernel path '%s' refers to directory, must be regular file, refusing.", kernel_image);
+
+                kernel_fd = xopenat_full(XAT_FDROOT, kernel_image, O_RDONLY|O_CLOEXEC, XO_REGULAR, MODE_INVALID);
+                if (kernel_fd < 0)
+                        return log_error_errno(kernel_fd, "Failed to open kernel image '%s': %m", kernel_image);
+
+        } else {
+                r = find_current_kernel(&kernel_filename, &kernel_fd);
+                if (r < 0)
+                        return r;
         }
 
+        context->kernel_filename = TAKE_PTR(kernel_filename);
+        context->kernel_fd = TAKE_FD(kernel_fd);
+
+        return 0;
+}
+
+static int sysinstall_context_run(SysInstallContext *context) {
+
+        int r;
+
+        assert(context);
+
+        (void) sysinstall_context_notify(context, PROGRESS_ENCRYPT_CREDENTIALS, NULL, UINT_MAX);
+
+        _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *creds_link = NULL;
+        _cleanup_strv_free_ char **encrypted_credentials = NULL;
+        r = encrypt_credentials(&creds_link, &context->credentials, &encrypted_credentials);
+        if (r < 0)
+                return r;
+
+        (void) sysinstall_context_notify(context, PROGRESS_INSTALL_PARTITIONS, NULL, UINT_MAX);
+
+        /* Do the main part of the installation */
+
+        r = sysinstall_context_invoke_repart_run(context);
+        if (r < 0)
+                return r;
+
+        (void) sysinstall_context_notify(context, PROGRESS_MOUNT_PARTITIONS, NULL, UINT_MAX);
+
+        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
+        _cleanup_(umount_and_freep) char *root_dir = NULL;
+        _cleanup_close_ int root_fd = -EBADF;
+        r = mount_image_privately_interactively(
+                        context->node,
+                        &image_policy,
+                        DISSECT_IMAGE_REQUIRE_ROOT |
+                        DISSECT_IMAGE_RELAX_VAR_CHECK |
+                        DISSECT_IMAGE_ALLOW_USERSPACE_VERITY |
+                        DISSECT_IMAGE_DISCARD_ANY |
+                        DISSECT_IMAGE_GPT_ONLY |
+                        DISSECT_IMAGE_FSCK |
+                        DISSECT_IMAGE_USR_NO_ROOT |
+                        DISSECT_IMAGE_ADD_PARTITION_DEVICES |
+                        DISSECT_IMAGE_PIN_PARTITION_DEVICES,
+                        &root_dir,
+                        &root_fd,
+                        &loop_device);
+        if (r < 0)
+                return log_error_errno(r, "Failed to mount new image: %m");
+
+        (void) sysinstall_context_notify(context, PROGRESS_INSTALL_KERNEL, NULL, UINT_MAX);
+
+        _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *bootctl_link = NULL;
+        r = invoke_bootctl_link(&bootctl_link, root_dir, root_fd, context->kernel_filename, context->kernel_fd, encrypted_credentials);
+        if (r < 0)
+                return r;
+
+        (void) sysinstall_context_notify(context, PROGRESS_INSTALL_BOOTLOADER, NULL, UINT_MAX);
+
+        r = invoke_bootctl_install(&bootctl_link, context->touch_variables, root_dir, root_fd);
+        if (r < 0)
+                return r;
+
+        (void) sysinstall_context_notify(context, PROGRESS_UNMOUNT_PARTITIONS, NULL, UINT_MAX);
+
+        root_fd = safe_close(root_fd);
+        r = umount_recursive(root_dir, /* flags= */ 0);
+        if (r < 0)
+                log_warning_errno(r, "Failed to unmount target disk, proceeding anyway: %m");
+        loop_device = loop_device_unref(loop_device);
+        sync();
+
+        return 0;
+}
+
+typedef struct ListCandidateDevicesContext {
+        char **definitions;
+        bool subscribe;
+
+        sd_varlink *repart_link; /* A repart connection to get candidate devices */
+        sd_varlink *dry_run_repart_link; /* A second repart connection to perform a dry run on each node */
+
+        sd_varlink *link;
+} ListCandidateDevicesContext;
+
+static ListCandidateDevicesContext* list_candidate_devices_context_new(void) {
+        ListCandidateDevicesContext *context = new(ListCandidateDevicesContext, 1);
+
+        if (!context)
+                return NULL;
+
+        *context = (ListCandidateDevicesContext) {};
+
+        return context;
+}
+
+static ListCandidateDevicesContext* list_candidate_devices_context_free(ListCandidateDevicesContext *context) {
+        if (!context)
+                return NULL;
+
+        strv_free(context->definitions);
+
+        context->repart_link = sd_varlink_flush_close_unref(context->repart_link);
+        context->dry_run_repart_link = sd_varlink_flush_close_unref(context->dry_run_repart_link);
+        context->link = sd_varlink_unref(context->link);
+
+        return mfree(context);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(ListCandidateDevicesContext*, list_candidate_devices_context_free);
+
+static int list_candidate_devices_context_settle_definitions(ListCandidateDevicesContext *context,
+                                                             char **definitions) {
+
+        return settle_definitions(definitions, &context->definitions);
+}
+
+static void vl_on_disconnect(sd_varlink_server *server, sd_varlink *link, void *userdata) {
+        assert(server);
+        assert(link);
+
+        list_candidate_devices_context_free(sd_varlink_set_userdata(link, NULL));
+}
+
+typedef struct DevicesResponse {
+        const char *node;
+        char **symlinks;
+        uint64_t diskseq;
+        uint64_t size;
+        const char *model;
+        const char *vendor;
+        const char *subsystem;
+        const char *action;
+} DevicesResponse;
+
+static void devices_response_done(DevicesResponse *p) {
+        assert(p);
+
+        p->symlinks = strv_free(p->symlinks);
+}
+
+static int fetch_candidate_devices_reply(
+                sd_varlink *repart_link,
+                sd_json_variant *reply,
+                const char *error_id,
+                sd_varlink_reply_flags_t flags,
+                void *userdata) {
+
+        int r;
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
+        ListCandidateDevicesContext *context = ASSERT_PTR(userdata);
+
+        if (error_id) {
+                if (streq(error_id, "io.systemd.Repart.NoCandidateDevices"))
+                        return sd_varlink_error(context->link, "io.systemd.SysInstall.NoCandidateDevices", NULL);
+
+                return sd_varlink_error(context->link, error_id, NULL);
+        }
+
+        static const sd_json_dispatch_field dispatch_table[] = {
+                { "node",      SD_JSON_VARIANT_STRING,        sd_json_dispatch_const_string, offsetof(DevicesResponse, node),      0 },
+                { "symlinks",  SD_JSON_VARIANT_ARRAY,         sd_json_dispatch_strv,         offsetof(DevicesResponse, symlinks),  0 },
+                { "diskseq",   _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,       offsetof(DevicesResponse, diskseq),   0 },
+                { "sizeBytes", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64,       offsetof(DevicesResponse, size),      0 },
+                { "model",     SD_JSON_VARIANT_STRING,        sd_json_dispatch_const_string, offsetof(DevicesResponse, model),     0 },
+                { "vendor",    SD_JSON_VARIANT_STRING,        sd_json_dispatch_const_string, offsetof(DevicesResponse, vendor),    0 },
+                { "subsystem", SD_JSON_VARIANT_STRING,        sd_json_dispatch_const_string, offsetof(DevicesResponse, subsystem), 0 },
+                { "action",    SD_JSON_VARIANT_STRING,        sd_json_dispatch_const_string, offsetof(DevicesResponse, action),    0 },
+                {}
+        };
+
+        _cleanup_(devices_response_done) DevicesResponse p = {
+                .diskseq = UINT64_MAX,
+                .size = UINT64_MAX,
+        };
+        r = sd_json_dispatch(reply, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p);
+        if (r < 0)
+                return r;
+
+        if (context->subscribe) {
+                /* The action needs to be ready, remove or add else we don't support the action */
+                if (streq(p.action, "ready"))
+                        return sd_varlink_notifybo(context->link, SD_JSON_BUILD_PAIR("action", JSON_BUILD_CONST_STRING("ready")));
+
+                if (streq(p.action, "remove"))
+                        return sd_varlink_notifybo(context->link,
+                                        SD_JSON_BUILD_PAIR("action", JSON_BUILD_CONST_STRING("remove")),
+                                        SD_JSON_BUILD_PAIR_STRING("node", p.node));
+
+                if (!streq(p.action, "add")) {
+                        log_debug("Skip unsupported action '%s' while fetching candidate devices.", p.action);
+                        return 0;
+                }
+        }
+
+        uint64_t min_size = UINT64_MAX, current_size = UINT64_MAX, need_free = UINT64_MAX;
+        r = invoke_repart(
+                        &context->dry_run_repart_link,
+                        p.node,
+                        /* erase= */ false,
+                        /* dry_run= */ true,
+                        context->definitions,
+                        &min_size,
+                        &current_size,
+                        &need_free);
+
+        DeviceFit fit;
+        if (r < 0) {
+                if (r == -ENOSPC)
+                        fit = DEVICE_FIT_INSUFFICIENT_FREE_SPACE;
+                else if (r == -E2BIG)
+                        fit = DEVICE_FIT_DISK_TOO_SMALL;
+                else if (r == -EHWPOISON)
+                        fit = DEVICE_FIT_CONFLICTING_DISK_LABEL_PRESENT;
+                else
+                        return r;
+        } else
+                fit = DEVICE_FIT_ENOUGH_FREE_SPACE;
+
+        r = sd_json_buildo(&v,
+                        SD_JSON_BUILD_PAIR_STRING("node", p.node),
+                        JSON_BUILD_PAIR_STRV_NON_EMPTY("symlinks", p.symlinks),
+                        JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("diskseq", p.diskseq, UINT64_MAX),
+                        JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("sizeBytes", p.size, UINT64_MAX),
+                        JSON_BUILD_PAIR_STRING_NON_EMPTY("model", p.model),
+                        JSON_BUILD_PAIR_STRING_NON_EMPTY("vendor", p.vendor),
+                        JSON_BUILD_PAIR_STRING_NON_EMPTY("subsystem", p.subsystem),
+                        JSON_BUILD_PAIR_ENUM("fit", device_fit_to_string(fit)),
+                        JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("currentSizeBytes", current_size, UINT64_MAX),
+                        JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("needFreeBytes", need_free, UINT64_MAX),
+                        JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("minimalSizeBytes", min_size, UINT64_MAX),
+                        SD_JSON_BUILD_PAIR_CONDITION(context->subscribe, "action", JSON_BUILD_CONST_STRING("add")));
+        if (r < 0)
+                return r;
+
+        if (FLAGS_SET(flags, SD_VARLINK_REPLY_CONTINUES))
+                return sd_varlink_notify(context->link, v);
+
+        return sd_varlink_reply(context->link, v);
+}
+
+typedef struct ListCandidateDevicesParameters {
+        char **definitions;
+        bool subscribe;
+} ListCandidateDevicesParameters;
+
+static void list_candidate_devices_parameters_done(ListCandidateDevicesParameters *p) {
+        assert(p);
+
+        p->definitions = strv_free(p->definitions);
+}
+
+static int vl_method_list_candidate_devices(
+                sd_varlink *link,
+                sd_json_variant *parameters,
+                sd_varlink_method_flags_t flags,
+                void *userdata) {
+        int r;
+
+        assert(link);
+
+        sd_varlink_server *varlink_server = sd_varlink_get_server(link);
+        sd_event *event = sd_varlink_server_get_event(varlink_server);
+        Hashmap **polkit_registry = ASSERT_PTR(sd_varlink_server_get_userdata(varlink_server));
+
+        assert(FLAGS_SET(flags, SD_VARLINK_METHOD_MORE));
+
+        r = varlink_verify_polkit_async(
+                        link,
+                        /* bus= */ NULL,
+                        "io.systemd.sysinstall.ListCandidateDevices",
+                        /* details= */ NULL,
+                        polkit_registry);
+        if (r <= 0)
+                return r;
+
+        static const sd_json_dispatch_field dispatch_table[] = {
+                { "definitions", SD_JSON_VARIANT_ARRAY,   json_dispatch_strv_path,  offsetof(ListCandidateDevicesParameters, definitions), SD_JSON_STRICT },
+                { "subscribe",   SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(ListCandidateDevicesParameters, subscribe),   0              },
+                {}
+        };
+
+        _cleanup_(list_candidate_devices_parameters_done) ListCandidateDevicesParameters p = {};
+        r = sd_varlink_dispatch(link, parameters, dispatch_table, &p);
+        if (r != 0)
+                return r;
+
+        _cleanup_(list_candidate_devices_context_freep) ListCandidateDevicesContext* context = list_candidate_devices_context_new();
+        if (!context)
+                return log_oom();
+
+        context->subscribe = p.subscribe;
+        r = list_candidate_devices_context_settle_definitions(context, p.definitions);
+        if (r < 0)
+                return r;
+
+        context->link = sd_varlink_ref(link);
+
+        r = connect_to_repart(&context->repart_link);
+        if (r < 0)
+                return r;
+
+        r = sd_varlink_attach_event(context->repart_link, event, SD_EVENT_PRIORITY_NORMAL);
+        if (r < 0)
+                return log_error_errno(
+                                r,
+                                "Failed to attach io.systemd.Repart.ListCandidateDevices() varlink connection to event loop: %m");
+
+        r = sd_varlink_bind_reply(context->repart_link, fetch_candidate_devices_reply);
+        if (r < 0)
+                return r;
+
+        r = sd_varlink_observebo(
+                        context->repart_link,
+                        "io.systemd.Repart.ListCandidateDevices",
+                        SD_JSON_BUILD_PAIR_BOOLEAN("subscribe", context->subscribe),
+                        SD_JSON_BUILD_PAIR_BOOLEAN("ignoreRoot", true));
+
+        if (r < 0)
+                return log_error_errno(
+                                r,
+                                "Failed to issue io.systemd.Repart.ListCandidateDevices() varlink call: %m");
+
+        r = sd_varlink_server_bind_disconnect(varlink_server, vl_on_disconnect);
+        if (r < 0)
+                return r;
+
+        /* The context is freed in vl_on_disconnect() */
+        sd_varlink_set_userdata(context->repart_link, context);
+        sd_varlink_set_userdata(link, TAKE_PTR(context));
+
+        return 0;
+}
+
+typedef struct RunParameters {
+        char *node;
+        char **definitions;
+        bool erase;
+        bool variables;
+        char *kernel_image;
+        bool copy_locale;
+        bool copy_keymap;
+        bool copy_timezone;
+        sd_json_variant *credentials;
+} RunParameters;
+
+static void run_parameters_done(RunParameters *p) {
+        assert(p);
+
+        p->node = mfree(p->node);
+        p->definitions = strv_free(p->definitions);
+        p->kernel_image = mfree(p->kernel_image);
+        p->credentials = sd_json_variant_unref(p->credentials);
+}
+
+typedef struct CredentialParameters {
+        const char *id;
+        struct iovec value;
+} CredentialParameters;
+
+static void credential_parameters_done(CredentialParameters *p) {
+        assert(p);
+
+        iovec_done_erase(&p->value);
+}
+
+static int credentials_from_json_array(MachineCredentialContext *credentials, sd_json_variant *v) {
+
+        int r;
+        sd_json_variant *credential;
+
+        assert(credentials);
+
+        static const sd_json_dispatch_field dispatch_table[] = {
+                { "id",    SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(CredentialParameters, id),    SD_JSON_MANDATORY },
+                { "value", SD_JSON_VARIANT_STRING, json_dispatch_unbase64_iovec,  offsetof(CredentialParameters, value), SD_JSON_MANDATORY },
+                {}
+        };
+
+        JSON_VARIANT_ARRAY_FOREACH(credential, v) {
+                _cleanup_(credential_parameters_done) CredentialParameters p = {};
+
+                r = sd_json_dispatch(credential, dispatch_table, /* flags= */ 0, &p);
+                if (r < 0)
+                        return r;
+
+                r = machine_credential_add(credentials, p.id, p.value.iov_base, p.value.iov_len);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
+
+static int vl_method_run(
+                sd_varlink *link,
+                sd_json_variant *parameters,
+                sd_varlink_method_flags_t flags,
+                void *userdata) {
+
+        static const sd_json_dispatch_field dispatch_table[] = {
+                { "node",            SD_JSON_VARIANT_STRING,  json_dispatch_path,       offsetof(RunParameters, node),          SD_JSON_MANDATORY | SD_JSON_STRICT },
+                { "definitions",     SD_JSON_VARIANT_ARRAY,   json_dispatch_strv_path,  offsetof(RunParameters, definitions),   SD_JSON_NULLABLE | SD_JSON_STRICT  },
+                { "erase",           SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(RunParameters, erase),         SD_JSON_MANDATORY                  },
+                { "variables",       SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(RunParameters, variables),     SD_JSON_NULLABLE                   },
+                { "kernelImagePath", SD_JSON_VARIANT_STRING,  json_dispatch_path,       offsetof(RunParameters, kernel_image),  SD_JSON_NULLABLE | SD_JSON_STRICT  },
+                { "copyLocale",      SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(RunParameters, copy_locale),   SD_JSON_NULLABLE                   },
+                { "copyKeymap",      SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(RunParameters, copy_keymap),   SD_JSON_NULLABLE                   },
+                { "copyTimezone",    SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(RunParameters, copy_timezone), SD_JSON_NULLABLE                   },
+                { "credentials",     SD_JSON_VARIANT_ARRAY,   sd_json_dispatch_variant, offsetof(RunParameters, credentials),   SD_JSON_NULLABLE                   },
+                {}
+        };
+
+        int r;
+
+        assert(link);
+
+        sd_varlink_server *varlink_server = sd_varlink_get_server(link);
+        Hashmap **polkit_registry = ASSERT_PTR(sd_varlink_server_get_userdata(varlink_server));
+
+        r = varlink_verify_polkit_async(
+                        link,
+                        /* bus= */ NULL,
+                        "io.systemd.sysinstall.Run",
+                        /* details= */ NULL,
+                        polkit_registry);
+        if (r <= 0)
+                return r;
+
+        _cleanup_(run_parameters_done) RunParameters p = {};
+        r = sd_varlink_dispatch(link, parameters, dispatch_table, &p);
+        if (r != 0)
+                return r;
+
+        _cleanup_(sysinstall_context_done) SysInstallContext context = (SysInstallContext) {
+                .copy_locale = p.copy_locale,
+                .copy_keymap = p.copy_keymap,
+                .copy_timezone = p.copy_timezone,
+                .erase = p.erase,
+                .touch_variables = p.variables,
+                .node = TAKE_PTR(p.node),
+                .kernel_fd = -EBADF,
+        };
+
+        r = sysinstall_context_settle_definitions(&context, p.definitions);
+        if (r < 0)
+                return r;
+
+        if (FLAGS_SET(flags, SD_VARLINK_METHOD_MORE))
+                context.link = sd_varlink_ref(link);
+
+        r = credentials_from_json_array(&context.credentials, p.credentials);
+        if (r < 0)
+                return r;
+
+        r = sysinstall_context_read_credentials(&context);
+        if (r < 0)
+                return r;
+
+        r = sysinstall_context_settle_kernel_image(&context, p.kernel_image);
+        if (r < 0)
+                return r;
+
+        r = sysinstall_context_run(&context);
+        if (r < 0)
+                return r;
+
+        return sd_varlink_reply(link, NULL);
+}
+
+static int vl_server(void) {
+        _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL;
+        _cleanup_hashmap_free_ Hashmap *polkit_registry = NULL;
+        int r;
+
+        /* Invocation as Varlink service */
+
+        r = varlink_server_new(
+                        &varlink_server,
+                        0,
+                        /* userdata= */ &polkit_registry);
+        if (r < 0)
+                return log_error_errno(r, "Failed to allocate Varlink server: %m");
+
+        r = sd_varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_SysInstall);
+        if (r < 0)
+                return log_error_errno(r, "Failed to add Varlink interface: %m");
+
+        r = sd_varlink_server_bind_method_many(
+                        varlink_server,
+                        "io.systemd.SysInstall.ListCandidateDevices", vl_method_list_candidate_devices,
+                        "io.systemd.SysInstall.Run",                  vl_method_run);
+        if (r < 0)
+                return log_error_errno(r, "Failed to bind Varlink methods: %m");
+
+        r = sd_varlink_server_loop_auto(varlink_server);
+        if (r < 0)
+                return log_error_errno(r, "Failed to run Varlink event loop: %m");
+
         return 0;
 }
 
@@ -1244,8 +2075,6 @@ static void end_marker(void) {
 }
 
 static int run(int argc, char *argv[]) {
-        _cleanup_free_ char *kernel_filename = NULL;
-        _cleanup_close_ int kernel_fd = -EBADF;
         int r;
 
         setlocale(LC_ALL, "");
@@ -1256,7 +2085,18 @@ static int run(int argc, char *argv[]) {
 
         log_setup();
 
-        r = settle_definitions();
+        if (arg_varlink)
+                return vl_server();
+
+        _cleanup_(sysinstall_context_done) SysInstallContext context = (SysInstallContext) {
+                .copy_locale = arg_copy_locale,
+                .copy_keymap = arg_copy_keymap,
+                .copy_timezone = arg_copy_timezone,
+                .credentials = TAKE_STRUCT(arg_credentials),
+                .kernel_fd = -EBADF,
+        };
+
+        r = sysinstall_context_settle_definitions(&context, arg_definitions);
         if (r < 0)
                 return r;
 
@@ -1291,6 +2131,7 @@ static int run(int argc, char *argv[]) {
                                 /* node= */ NULL,
                                 /* erase= */ true,
                                 /* dry_run= */ true,
+                                arg_definitions,
                                 &min_size,
                                 /* current_size= */ NULL,
                                 /* need_free= */ NULL);
@@ -1324,34 +2165,28 @@ static int run(int argc, char *argv[]) {
         if (r < 0)
                 return r;
 
-        r = read_credentials();
+        r = sysinstall_context_read_credentials(&context);
         if (r < 0)
                 return r;
 
-        if (arg_kernel_image) {
-                r = path_extract_filename(arg_kernel_image, &kernel_filename);
-                if (r < 0)
-                        return log_error_errno(r, "Failed to extract filename from kernel path '%s': %m", arg_kernel_image);
-                if (r == O_DIRECTORY)
-                        return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Kernel path '%s' refers to directory, must be regular file, refusing.", arg_kernel_image);
-
-                kernel_fd = xopenat_full(XAT_FDROOT, arg_kernel_image, O_RDONLY|O_CLOEXEC, XO_REGULAR, MODE_INVALID);
-                if (kernel_fd < 0)
-                        return log_error_errno(kernel_fd, "Failed to open kernel image '%s': %m", arg_kernel_image);
-        } else {
-                r = find_current_kernel(&kernel_filename, &kernel_fd);
-                if (r < 0)
-                        return r;
-        }
+        r = sysinstall_context_settle_kernel_image(&context, arg_kernel_image);
+        if (r < 0)
+                return r;
 
         /* Verify we have everything we need */
         assert(arg_node);
         assert(arg_erase >= 0);
         assert(arg_touch_variables >= 0);
 
-        r = show_summary();
-        if (r < 0)
-                return r;
+        context.node = TAKE_PTR(arg_node);
+        context.touch_variables = arg_touch_variables;
+        context.erase = arg_erase;
+
+        if (arg_summary) {
+                r = sysinstall_context_show_summary(&context);
+                if (r < 0)
+                        return r;
+        }
 
         r = prompt_confirm();
         if (r < 0)
@@ -1359,79 +2194,10 @@ static int run(int argc, char *argv[]) {
 
         putchar('\n');
 
-        log_notice("%s%sEncrypting credentials...",
-                   emoji_enabled() ? glyph(GLYPH_LOCK_AND_KEY) : "", emoji_enabled() ? " " : "");
-
-        _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *creds_link = NULL;
-        _cleanup_strv_free_ char **encrypted_credentials = NULL;
-        r = encrypt_credentials(&creds_link, &encrypted_credentials);
-        if (r < 0)
-                return r;
-
-        log_notice("%s%sInstalling partitions...",
-                   emoji_enabled() ? glyph(GLYPH_COMPUTER_DISK) : "", emoji_enabled() ? " " : "");
-
-        /* Do the main part of the installation */
-        r = invoke_repart(
-                        &repart_link,
-                        arg_node,
-                        arg_erase,
-                        /* dry_run= */ false,
-                        /* min_size= */ NULL,
-                        /* current_size= */ NULL,
-                        /* need_free= */ NULL);
-        if (r < 0)
-                return r;
-
-        log_notice("%s%sMounting partitions...",
-                   emoji_enabled() ? glyph(GLYPH_COMPUTER_DISK) : "", emoji_enabled() ? " " : "");
-
-        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
-        _cleanup_(umount_and_freep) char *root_dir = NULL;
-        _cleanup_close_ int root_fd = -EBADF;
-        r = mount_image_privately_interactively(
-                        arg_node,
-                        &image_policy,
-                        DISSECT_IMAGE_REQUIRE_ROOT |
-                        DISSECT_IMAGE_RELAX_VAR_CHECK |
-                        DISSECT_IMAGE_ALLOW_USERSPACE_VERITY |
-                        DISSECT_IMAGE_DISCARD_ANY |
-                        DISSECT_IMAGE_GPT_ONLY |
-                        DISSECT_IMAGE_FSCK |
-                        DISSECT_IMAGE_USR_NO_ROOT |
-                        DISSECT_IMAGE_ADD_PARTITION_DEVICES |
-                        DISSECT_IMAGE_PIN_PARTITION_DEVICES,
-                        &root_dir,
-                        &root_fd,
-                        &loop_device);
-        if (r < 0)
-                return log_error_errno(r, "Failed to mount new image: %m");
-
-        log_notice("%s%sInstalling kernel...",
-                   emoji_enabled() ? glyph(GLYPH_COMPUTER_DISK) : "", emoji_enabled() ? " " : "");
-
-        _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *bootctl_link = NULL;
-        r = invoke_bootctl_link(&bootctl_link, root_dir, root_fd, kernel_filename, kernel_fd, encrypted_credentials);
+        r = sysinstall_context_run(&context);
         if (r < 0)
                 return r;
 
-        log_notice("%s%sInstalling boot loader...",
-                   emoji_enabled() ? glyph(GLYPH_COMPUTER_DISK) : "", emoji_enabled() ? " " : "");
-
-        r = invoke_bootctl_install(&bootctl_link, root_dir, root_fd);
-        if (r < 0)
-                return r;
-
-        log_notice("%s%sUnmounting partitions...",
-                   emoji_enabled() ? glyph(GLYPH_COMPUTER_DISK) : "", emoji_enabled() ? " " : "");
-
-        root_fd = safe_close(root_fd);
-        r = umount_recursive(root_dir, /* flags= */ 0);
-        if (r < 0)
-                log_warning_errno(r, "Failed to unmount target disk, proceeding anyway: %m");
-        loop_device = loop_device_unref(loop_device);
-        sync();
-
         log_notice("%s%sInstallation succeeded.",
                    emoji_enabled() ? glyph(GLYPH_SPARKLES) : "", emoji_enabled() ? " " : "");
 
index 569aa2e2a2cf0439b200ccbd2877dfa099c15536..615ddd80ae7af327823a1add1b2f1facce828b8d 100644 (file)
@@ -840,6 +840,15 @@ units = [
           'conditions' : ['ENABLE_SYSINSTALL'],
           'symlinks' : ['system-install.target.wants/'],
         },
+        {
+          'file' : 'systemd-sysinstall.socket',
+          'conditions' : ['ENABLE_SYSINSTALL'],
+          'symlinks' : ['sockets.target.wants/'],
+        },
+        {
+          'file' : 'systemd-sysinstall@.service',
+          'conditions' : ['ENABLE_SYSINSTALL'],
+        },
         {
           'file' : 'systemd-sysupdate-reboot.service',
           'conditions' : ['ENABLE_SYSUPDATE'],
index a330db2ef30545a7da123a5bb46eddbf8a510a1f..6b25fed13d1464ef922ce609c206a060952d787a 100644 (file)
@@ -14,6 +14,8 @@ Wants=systemd-logind.service
 After=systemd-logind.service
 
 [Service]
+Type=oneshot
+RemainAfterExit=yes
 ExecStart=systemd-sysinstall --variables=yes --reboot=yes --mute-console=yes
 StandardOutput=tty
 StandardInput=tty
diff --git a/units/systemd-sysinstall.socket b/units/systemd-sysinstall.socket
new file mode 100644 (file)
index 0000000..5eb5f86
--- /dev/null
@@ -0,0 +1,27 @@
+#  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)
+DefaultDependencies=no
+Before=sockets.target
+Conflicts=shutdown.target
+Before=shutdown.target
+
+[Socket]
+ListenStream=/run/systemd/io.systemd.SysInstall
+Symlinks=/run/varlink/registry/io.systemd.SysInstall
+FileDescriptorName=varlink
+SocketMode=0666
+Accept=yes
+MaxConnectionsPerSource=16
+XAttrEntryPoint=user.varlink=entrypoint
+XAttrListen=user.varlink=listen
+XAttrAccept=user.varlink=server
diff --git a/units/systemd-sysinstall@.service b/units/systemd-sysinstall@.service
new file mode 100644 (file)
index 0000000..54cb72c
--- /dev/null
@@ -0,0 +1,20 @@
+#  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)
+DefaultDependencies=no
+Wants=modprobe@loop.service modprobe@dm_mod.service
+After=modprobe@loop.service modprobe@dm_mod.service systemd-tpm2-setup-early.service
+Conflicts=shutdown.target
+Before=shutdown.target
+
+[Service]
+ExecStart=systemd-sysinstall