vmspawn: propagate $TERM from host into VM via kernel command line

When running in a console mode (interactive, native, or read-only),
propagate the host's $TERM into the VM by adding TERM= and
systemd.tty.term.hvc0= to the kernel command line.

TERM= is picked up by PID 1 and inherited by services on /dev/console
(such as emergency.service). systemd.tty.term.hvc0= is used by services
directly attached to /dev/hvc0 (such as serial-getty@hvc0.service) which
look up $TERM via the systemd.tty.term.<tty> kernel command line
parameter.

While systemd can auto-detect the terminal type via DCS XTGETTCAP, not
all terminal emulators implement this, so explicitly propagating $TERM
provides a more reliable experience. We skip propagation when $TERM is
unset or set to "unknown" (as is the case in GitHub Actions and some
other CI environments).

Previously this was handled by mkosi synthesizing the corresponding
kernel command line parameters externally.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Daan De Meyer
2026-04-02 18:06:10 +02:00
committed by Daan De Meyer
parent 214f7f0b8a
commit dcce04e649
3 changed files with 29 additions and 0 deletions
+10
View File
@@ -1713,6 +1713,16 @@ static bool on_dev_null(void) {
return cached_on_dev_null;
}
bool term_env_valid(const char *term) {
/* Checks if the specified $TERM value is suitable for propagation, i.e. is not empty, not set to
* "unknown" (as is common in CI), and only contains characters valid in terminal type names.
* Valid $TERM values are things like "xterm-256color", "linux", "screen.xterm-256color", i.e.
* alphanumeric characters, hyphens, underscores, dots, and plus signs. */
return !isempty(term) &&
!streq(term, "unknown") &&
in_charset(term, ALPHANUMERICAL "-_+.");
}
bool getenv_terminal_is_dumb(void) {
const char *e;
+1
View File
@@ -118,6 +118,7 @@ void columns_lines_cache_reset(int _unused_ signum);
void reset_terminal_feature_caches(void);
bool on_tty(void);
bool term_env_valid(const char *term);
bool getenv_terminal_is_dumb(void);
bool terminal_is_dumb(void);
+18
View File
@@ -3031,6 +3031,24 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
r = strv_prepend(&arg_kernel_cmdline_extra, "console=hvc0");
if (r < 0)
return log_oom();
/* Propagate the host's $TERM into the VM via the kernel command line. TERM= is
* picked up by PID 1 and inherited by services on /dev/console, and
* systemd.tty.term.hvc0= is used by services directly attached to /dev/hvc0 (such
* as serial-getty). While systemd can auto-detect the terminal type via DCS
* XTGETTCAP, not all terminal emulators implement this, so let's always propagate
* $TERM if we have it. */
const char *term = getenv("TERM");
if (term_env_valid(term)) {
FOREACH_STRING(tty_key, "systemd.tty.term.hvc0", "TERM") {
_cleanup_free_ char *p = strjoin(tty_key, "=", term);
if (!p)
return log_oom();
if (strv_consume_prepend(&arg_kernel_cmdline_extra, TAKE_PTR(p)) < 0)
return log_oom();
}
}
}
_cleanup_free_ char *fstab_extra = NULL;