diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml
index d7fee0538ac..c9ad82b2f02 100644
--- a/man/systemd-vmspawn.xml
+++ b/man/systemd-vmspawn.xml
@@ -274,6 +274,41 @@
+
+
+
+
+ Set the specified UUID for the virtual machine. The
+ init system will initialize
+ /etc/machine-id from this if this file is
+ not set yet. Note that this option takes effect only if
+ /etc/machine-id in the virtual machine is
+ unpopulated.
+
+
+
+
+
+
+
+ Property Options
+
+
+
+
+
+ Controls whether the virtual machine is registered with
+ systemd-machined8. Takes a
+ boolean argument, which defaults to yes when running as root, and no when
+ running as a regular user. This ensures that the virtual machine is accessible via
+ machinectl1.
+
+ Note: root privileges are required to use this option as registering with
+ systemd-machined8
+ requires privileged D-Bus method calls.
+
+
+
diff --git a/src/vmspawn/meson.build b/src/vmspawn/meson.build
index 10810afa81d..3cd9a3b69e6 100644
--- a/src/vmspawn/meson.build
+++ b/src/vmspawn/meson.build
@@ -5,6 +5,7 @@ libvmspawn_core_sources = files(
'vmspawn-util.c',
'vmspawn-scope.c',
'vmspawn-mount.c',
+ 'vmspawn-register.c',
)
libvmspawn_core = static_library(
'vmspawn-core',
diff --git a/src/vmspawn/vmspawn-register.c b/src/vmspawn/vmspawn-register.c
new file mode 100644
index 00000000000..d04c3dafad9
--- /dev/null
+++ b/src/vmspawn/vmspawn-register.c
@@ -0,0 +1,51 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "sd-bus.h"
+#include "sd-id128.h"
+
+#include "bus-error.h"
+#include "bus-locator.h"
+#include "macro.h"
+#include "process-util.h"
+#include "string-util.h"
+#include "vmspawn-register.h"
+
+int register_machine(sd_bus *bus, const char *machine_name, sd_id128_t uuid, const char *service, const char *directory) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ int r;
+
+ assert(bus);
+ assert(machine_name);
+ assert(service);
+
+ r = bus_call_method(
+ bus,
+ bus_machine_mgr,
+ "RegisterMachine",
+ &error,
+ NULL,
+ "sayssus",
+ machine_name,
+ SD_BUS_MESSAGE_APPEND_ID128(uuid),
+ service,
+ "vm",
+ (uint32_t) getpid_cached(),
+ strempty(directory));
+ if (r < 0)
+ return log_error_errno(r, "Failed to register machine: %s", bus_error_message(&error, r));
+
+ return 0;
+}
+
+int unregister_machine(sd_bus *bus, const char *machine_name) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ int r;
+
+ assert(bus);
+
+ r = bus_call_method(bus, bus_machine_mgr, "UnregisterMachine", &error, NULL, "s", machine_name);
+ if (r < 0)
+ log_debug("Failed to unregister machine: %s", bus_error_message(&error, r));
+
+ return 0;
+}
diff --git a/src/vmspawn/vmspawn-register.h b/src/vmspawn/vmspawn-register.h
new file mode 100644
index 00000000000..7aa82ce849a
--- /dev/null
+++ b/src/vmspawn/vmspawn-register.h
@@ -0,0 +1,7 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "sd-bus.h"
+#include "sd-id128.h"
+
+int register_machine(sd_bus *bus, const char *machine_name, sd_id128_t uuid, const char *service, const char *directory);
+int unregister_machine(sd_bus *bus, const char *machine_name);
diff --git a/src/vmspawn/vmspawn-settings.h b/src/vmspawn/vmspawn-settings.h
index fe23aa23cf7..5446c20911c 100644
--- a/src/vmspawn/vmspawn-settings.h
+++ b/src/vmspawn/vmspawn-settings.h
@@ -17,6 +17,7 @@ typedef enum ConsoleMode {
typedef enum SettingsMask {
SETTING_START_MODE = UINT64_C(1) << 0,
+ SETTING_MACHINE_ID = UINT64_C(1) << 6,
SETTING_BIND_MOUNTS = UINT64_C(1) << 11,
SETTING_DIRECTORY = UINT64_C(1) << 26,
SETTING_CREDENTIALS = UINT64_C(1) << 30,
diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c
index ce7f1ef2e3e..e1e4350b843 100644
--- a/src/vmspawn/vmspawn.c
+++ b/src/vmspawn/vmspawn.c
@@ -57,6 +57,7 @@
#include "tmpfile-util.h"
#include "unit-name.h"
#include "vmspawn-mount.h"
+#include "vmspawn-register.h"
#include "vmspawn-scope.h"
#include "vmspawn-settings.h"
#include "vmspawn-util.h"
@@ -86,6 +87,8 @@ static char *arg_runtime_directory = NULL;
static char *arg_forward_journal = NULL;
static bool arg_runtime_directory_created = false;
static bool arg_privileged = false;
+static bool arg_register = false;
+static sd_id128_t arg_uuid = {};
static char **arg_kernel_cmdline_extra = NULL;
static char **arg_extra_drives = NULL;
static char *arg_background = NULL;
@@ -139,6 +142,9 @@ static int help(void) {
" --firmware=PATH|list Select firmware definition file (or list available)\n"
"\n%3$sSystem Identity:%4$s\n"
" -M --machine=NAME Set the machine name for the VM\n"
+ " --uuid=UUID Set a specific machine UUID for the VM\n"
+ "\n%3$sProperties:%4$s\n"
+ " --register=BOOLEAN Register VM with systemd-machined\n"
"\n%3$sUser Namespacing:%4$s\n"
" --private-users=UIDBASE[:NUIDS]\n"
" Configure the UID/GID range to map into the\n"
@@ -186,6 +192,8 @@ static int parse_argv(int argc, char *argv[]) {
ARG_INITRD,
ARG_QEMU_GUI,
ARG_NETWORK_USER_MODE,
+ ARG_UUID,
+ ARG_REGISTER,
ARG_BIND,
ARG_BIND_RO,
ARG_EXTRA_DRIVE,
@@ -223,6 +231,8 @@ static int parse_argv(int argc, char *argv[]) {
{ "qemu-gui", no_argument, NULL, ARG_QEMU_GUI }, /* compat option */
{ "network-tap", no_argument, NULL, 'n' },
{ "network-user-mode", no_argument, NULL, ARG_NETWORK_USER_MODE },
+ { "uuid", required_argument, NULL, ARG_UUID },
+ { "register", required_argument, NULL, ARG_REGISTER },
{ "bind", required_argument, NULL, ARG_BIND },
{ "bind-ro", required_argument, NULL, ARG_BIND_RO },
{ "extra-drive", required_argument, NULL, ARG_EXTRA_DRIVE },
@@ -372,6 +382,26 @@ static int parse_argv(int argc, char *argv[]) {
arg_network_stack = NETWORK_STACK_USER;
break;
+ case ARG_UUID:
+ r = id128_from_string_nonzero(optarg, &arg_uuid);
+ if (r == -ENXIO)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Machine UUID may not be all zeroes.");
+ if (r < 0)
+ return log_error_errno(r, "Invalid UUID: %s", optarg);
+
+ arg_settings_mask |= SETTING_MACHINE_ID;
+ break;
+
+ case ARG_REGISTER:
+ r = parse_boolean(optarg);
+ if (r < 0) {
+ log_error("Failed to parse --register= argument: %s", optarg);
+ return r;
+ }
+
+ arg_register = r;
+ break;
+
case ARG_BIND:
case ARG_BIND_RO:
r = runtime_mount_parse(&arg_runtime_mounts, optarg, c == ARG_BIND_RO);
@@ -1093,6 +1123,12 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
if (r < 0)
return r;
+ if (arg_register) {
+ r = register_machine(bus, arg_machine, arg_uuid, trans_scope, arg_directory);
+ if (r < 0)
+ return r;
+ }
+
bool use_kvm = arg_kvm > 0;
if (arg_kvm < 0) {
r = qemu_check_kvm_support();
@@ -1747,6 +1783,9 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
if (r < 0)
return log_error_errno(r, "Failed to run event loop: %m");
+ if (arg_register)
+ (void) unregister_machine(bus, arg_machine);
+
if (use_vsock) {
if (exit_status == INT_MAX) {
log_debug("Couldn't retrieve inner EXIT_STATUS from VSOCK");
@@ -1825,6 +1864,9 @@ static int verify_arguments(void) {
if (!strv_isempty(arg_initrds) && !arg_linux)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --initrd= cannot be used without --linux=.");
+ if (arg_register && !arg_privileged)
+ return log_error_errno(SYNTHETIC_ERRNO(EPERM), "--register= requires root privileges, refusing.");
+
return 0;
}
@@ -1836,6 +1878,9 @@ static int run(int argc, char *argv[]) {
arg_privileged = getuid() == 0;
+ /* don't attempt to register as a machine when running as a user */
+ arg_register = arg_privileged;
+
r = parse_argv(argc, argv);
if (r <= 0)
return r;