]
# Underscores are not allowed in machine names so replace them with hyphens.
- name = config.name().replace("_", "-")
+ name = config.machine_or_name().replace("_", "-")
cmdline += ["--machine", name]
for k, v in config.credentials.items():
runtime_network: Network
ssh_key: Optional[Path]
ssh_certificate: Optional[Path]
+ machine: Optional[str]
vmm: Vmm
# QEMU-specific options
def name(self) -> str:
return self.image_id or self.image or "default"
+ def machine_or_name(self) -> str:
+ return self.machine or self.name()
+
def output_dir_or_cwd(self) -> Path:
return self.output_dir or Path.cwd()
default=Vmm.qemu,
help="Set the virtual machine monitor to use for mkosi qemu",
),
+ ConfigSetting(
+ dest="machine",
+ metavar="NAME",
+ section="Host",
+ help="Set the machine name to use when booting the image",
+ ),
ConfigSetting(
dest="qemu_gui",
metavar="BOOL",
metavar="NUMBER|auto|hash",
section="Host",
parse=config_parse_vsock_cid,
- default=QemuVsockCID.hash,
+ default=QemuVsockCID.auto,
help="Specify the VSock connection ID to use",
),
ConfigSetting(
if not any(s.startswith("SYSTEMD_SULOGIN_FORCE=") for s in args.kernel_command_line_extra):
cmdline += ["SYSTEMD_SULOGIN_FORCE=1"]
+ if not any(s.startswith("systemd.hostname=") for s in args.kernel_command_line_extra) and args.machine:
+ cmdline += [f"systemd.hostname={args.machine}"]
+
if args.qemu_cdrom:
# CD-ROMs are read-only so tell systemd to boot in volatile mode.
cmdline += ["systemd.volatile=yes"]
Runtime Network: {config.runtime_network}
SSH Signing Key: {none_to_none(config.ssh_key)}
SSH Certificate: {none_to_none(config.ssh_certificate)}
+ Machine: {config.machine_or_name()}
Virtual Machine Monitor: {config.vmm}
QEMU GUI: {yes_no(config.qemu_gui)}
yield Path(file.name)
+@contextlib.contextmanager
+def finalize_state(config: Config, cid: int) -> Iterator[None]:
+ (INVOKING_USER.runtime_dir() / "machine").mkdir(parents=True, exist_ok=True)
+
+ if INVOKING_USER.is_regular_user():
+ os.chown(INVOKING_USER.runtime_dir(), INVOKING_USER.uid, INVOKING_USER.gid)
+ os.chown(INVOKING_USER.runtime_dir() / "machine", INVOKING_USER.uid, INVOKING_USER.gid)
+
+ with flock(INVOKING_USER.runtime_dir() / "machine"):
+ if (p := INVOKING_USER.runtime_dir() / "machine" / f"{config.machine_or_name()}.json").exists():
+ die(f"Another virtual machine named {config.machine_or_name()} is already running",
+ hint="Use --machine to specify a different virtual machine name")
+
+ p.write_text(
+ json.dumps(
+ {
+ "Machine": config.machine_or_name(),
+ "ProxyCommand": f"socat - VSOCK-CONNECT:{cid}:%p",
+ "SshKey": os.fspath(config.ssh_key) if config.ssh_key else None,
+ },
+ sort_keys=True,
+ indent=4,
+ )
+ )
+
+ if INVOKING_USER.is_regular_user():
+ os.chown(p, INVOKING_USER.uid, INVOKING_USER.gid)
+
+ try:
+ yield
+ finally:
+ with flock(INVOKING_USER.runtime_dir() / "machine"):
+ p.unlink(missing_ok=True)
+
+
def run_qemu(args: Args, config: Config) -> None:
if config.output_format not in (
OutputFormat.disk,
cmdline += ["-accel", accel]
+ cid: Optional[int] = None
if QemuDeviceNode.vhost_vsock in qemu_device_fds:
if config.qemu_vsock_cid == QemuVsockCID.auto:
cid = find_unused_vsock_cid(config, qemu_device_fds[QemuDeviceNode.vhost_vsock])
cmdline += config.qemu_args
cmdline += args.cmdline
+ if cid is not None:
+ stack.enter_context(finalize_state(config, cid))
+
with spawn(
cmdline,
stdin=sys.stdin,
def run_ssh(args: Args, config: Config) -> None:
- if config.qemu_vsock_cid == QemuVsockCID.auto:
- die("Can't use ssh verb with QemuVSockCID=auto")
+ with flock(INVOKING_USER.runtime_dir() / "machine"):
+ if not (p := INVOKING_USER.runtime_dir() / "machine" / f"{config.machine_or_name()}.json").exists():
+ die(f"{p} not found, cannot SSH into virtual machine {config.machine_or_name()}",
+ hint="Is the machine running and was it built with Ssh=yes and QemuVsock=yes?")
- if not config.ssh_key:
- die("SshKey= must be configured to use 'mkosi ssh'",
- hint="Use 'mkosi genkey' to generate a new SSH key and certificate")
+ state = json.loads(p.read_text())
- if config.qemu_vsock_cid == QemuVsockCID.hash:
- cid = hash_to_vsock_cid(hash_output(config))
- else:
- cid = config.qemu_vsock_cid
+ if not state["SshKey"]:
+ die("An SSH key must be configured when booting the image to use 'mkosi ssh'",
+ hint="Use 'mkosi genkey' to generate a new SSH key and certificate")
cmd: list[PathString] = [
"ssh",
- "-i", config.ssh_key,
+ "-i", state["SshKey"],
"-F", "none",
# Silence known hosts file errors/warnings.
"-o", "UserKnownHostsFile=/dev/null",
"-o", "StrictHostKeyChecking=no",
"-o", "LogLevel=ERROR",
- "-o", f"ProxyCommand=socat - VSOCK-CONNECT:{cid}:%p",
+ "-o", f"ProxyCommand={state['ProxyCommand']}",
"root@mkosi",
]
arguments to the `ssh` invocation. To connect to a container, use
`machinectl login` or `machinectl shell`.
+: The `Machine=` option can be used to give the machine a custom
+ hostname when booting it which can later be used to ssh into the image
+ (e.g. `mkosi --machine=mymachine qemu` followed by
+ `mkosi --machine=mymachine ssh`).
+
`journalctl`
: Uses `journalctl` to inspect the journal inside the image.
: When used with the `qemu` verb, this option specifies the vsock
connection ID to use. Takes a number in the interval `[3, 0xFFFFFFFF)`
- or `hash` or `auto`. Defaults to `hash`. When set to `hash`, the
+ or `hash` or `auto`. Defaults to `auto`. When set to `hash`, the
connection ID will be derived from the full path to the image. When
set to `auto`, `mkosi` will try to find a free connection ID
automatically. Otherwise, the provided number will be used as is.
-: Note that when set to `auto`, `mkosi ssh` cannot be used as we cannot
- figure out which free connection ID we found when booting the image
- earlier.
-
`QemuSwtpm=`, `--qemu-swtpm=`
: When used with the `qemu` verb, this option specifies whether to start an instance of swtpm to be used as a
automatically be used for this purpose. Run `mkosi genkey` to
automatically generate a certificate in `mkosi.crt`.
+`Machine=`, `--machine=`
+
+: Specify the machine name to use when booting the image. Can also be
+ used to refer to a specific image when SSH-ing into an image (e.g.
+ `mkosi --image=myimage ssh`).
+
+: Note that `Ephemeral=` has to be enabled to start multiple instances
+ of the same image.
+
## Specifiers
The current value of various settings can be accessed when parsing
return cache / "mkosi"
+ @classmethod
+ def runtime_dir(cls) -> Path:
+ if (env := os.getenv("XDG_RUNTIME_DIR")) or (env := os.getenv("RUNTIME_DIRECTORY")):
+ d = Path(env)
+ elif cls.is_regular_user():
+ d = Path("/run/user") / str(cls.uid)
+ else:
+ d = Path("/run")
+
+ return d / "mkosi"
+
@classmethod
def rchown(cls, path: Path) -> None:
if cls.is_regular_user() and any(p.stat().st_uid == cls.uid for p in path.parents) and path.exists():
raise e
die(f"Cannot lock {path} as it is locked by another process",
- hint="Maybe another mkosi process is still using it?")
+ hint="Maybe another mkosi process is still using it? Use Ephemeral=yes to enable booting multiple "
+ "instances of the same image")
@contextlib.contextmanager
"LocalMirror": null,
"Locale": "en_C.UTF-8",
"LocaleMessages": "",
+ "Machine": "machine",
"MakeInitrd": false,
"ManifestFormat": [
"json",
local_mirror = None,
locale = "en_C.UTF-8",
locale_messages = "",
+ machine = "machine",
make_initrd = False,
manifest_format = [ManifestFormat.json, ManifestFormat.changelog],
minimum_version = GenericVersion("123"),