Ir al contenido

Configurar Serial Console TTY en Proxmox

·6 mins
Linux Virtualización Proxmox serial console TTY Systemd Upstart virtualización KVM Debian Ubuntu
Jordi Fàbregas
Autor
Jordi Fàbregas
Administrador de Sistemas y Redes. Entusiasta del software libre.
Tabla de contenido

Terminal o terminal connection

Introducción
#

Usando Proxmox es muy útil la función de habilitar el puerto serial de las máquinas virtuales para poder comunicarse directamente con la consola sin usar SSH o noVNC.

Esto nos permite algunas ventajas interesantes.

La primera es tener una opción alternativa a noVNC, ya que esta saca la imagen en video de la pantalla. Este comportamiento a veces puede ser molesto, sobre todo cuando se trabajan con tamaños de ventana pequeños ya que se puede mostrar muy distorsionada la pantalla. De otro modo, noVNC no nos permite copiar y pegar texto en la terminal, cosa que para mi resulta muy útil.

Una solución a este problema podría ser habilitar el servicio de SSH, pero esto tiene implicaciones como tener que abrir puertos, configurar el firewall, reglas de autenticación, fail2ban, etc.

Proxmox ofrece, de forma nativa, un terminal serial (basado en xterm.js) accesible desde el propio nodo en lugar de noVNC. En contenedores LXC esta funcionalidad ya viene preconfigurada, pero en máquinas virtuales (KVM/QEMU) es necesario habilitarla manualmente.

Desde la wiki oficial de Proxmox, nos proponen este método de instalación, un método basado en Upstart, que está en desuso desde Ubuntu 15.04 y solo se mantiene por retro­compatibilidad. A continuación se muestra cómo hacerlo usando systemd, de forma más sencilla y adaptada a distribuciones modernas.

Configuración en el nodo de Proxmox
#

Primero en el nodo de Proxmox, con la máquina virtual apagada, ejecutamos en la terminal:

qm set <VM-ID> -serial0 socket

En dónde substituiremos <VM-ID> por el número de ID de la máquina virtual a la que queramos añadir la funcionalidad. Una vez hayamos ejecutado esta orden en el nodo de proxmox, ya podremos iniciar la máquina virtual.

Configuración en la máquina virtual (VM)
#

Estas instrucciones estan pensadas para un equipo que utilize systemd como init system. Estás instrucciones no funcionarán en máquinas que utilicen otros sistemas de init.

Ya desde la terminal de la máquina virtual, debemos configurar el servicio getty en systemd

systemctl enable serial-getty@ttyS0.service
systemctl start serial-getty@ttyS0.service

Finalmente, tendremos que editar el archivo /etc/default/grub , el cual es un fichero crucial del sistema, por lo que será recomendable realizar una copia de seguridad del mismo.

En este fichero buscaremos la línea: GRUB_CMDLINE_LINUX=""

Y la cambiaremos por lo siguiente: GRUB_CMDLINE_LINUX="quiet console=tty0 console=ttyS0,115200"

Finalmente, regeneraremos grub con sudo update-grub en entornos con base Debian o sudo grub2-mkconfig en entornos RHEL, reiniciaremos la máquina virtual y ya debería de funcionar.

Script de automatización
#

Para facilitar la instalación en entornos con Debian/Ubuntu con systemd o Upstart como init system he realizado el siguiente script en python para ejecutar en la VM.

Los pasos a ejecutar en el nodo, se deberán seguir igualmente de forma manual.

Este script detecta si se usa Upstart o Systemd, crea un backup del fichero /etc/default/grub en backup/grub, edita el fichero grub, regenera la configuración de grub y configura automáticamente Upstart o Systemd según se haya detectado.

ADVERTENCIA: Este script está diseñado para uso personal del autor. Usar bajo propio riesgo y cuenta. Siempre haz una copia de seguridad y verifica que los puntos de restauración funcionan correctamente.
##############################################################################
# Licensed under the GNU GPL v3 (https://www.gnu.org/licenses/gpl-3.0.html)
# Filename: configure_serial_console.py
# Version: 1.0
# Author: Jordi Fàbregas
# Contributors:
##############################################################################

'''
**WARNING:**  
Use at your own risk. Always back up and verify system restore before running.

Configure serial console access on Linux virtual machines running on Proxmox Virtual Environment (Proxmox VE).

Compatible only with Debian-based Linux systems that use Systemd or Upstart as the init systems.

Only tested with Debian and Ubuntu.

This script detects the init system in use (Systemd or Upstart),
creates a backup of the GRUB configuration file, updates the serial console parameters in grub,
and configures the appropriate service to enable a serial getty on ttyS0.

Proxmox Node Configuration:
From the Proxmox node shell, with the virtual machine powered off, execute:
    qm set <VMID> -serial0 socket
Then, start the virtual machine.

Usage (inside the virtual machine):
Run this script as root or with sudo:
    sudo python3 configure_serial_console.py
'''

import subprocess
import sys
import os

GRUB_CONF_FILE = '/etc/default/grub'
UPSTART_PATH = '/etc/init/'
UPSTART_TTY_FILE = 'ttyS0.conf'
BACKUP_PATH = 'backup/'
BACKUP_SUFIX = '.backup'
GRUB_CMDLINE_LINUX = 'quiet console=tty0 console=ttyS0,115200'

def check_root():
    '''
    Check if the script is executed with superuser (root) privileges.
    Input: None
    Output: Boolean
    '''
    if os.geteuid() == 0:
        return True
    else:
        return False

def check_systemd():
    '''
    Check if init system is systemd
    Input: None
    Output: Boolean
    '''
    if os.path.exists('/run/systemd/system'):
        return True
    else:
        return False

def check_upstart():
    '''
    Check if init systems is upstart
    Input: None
    Output: Boolean
    '''
    if os.path.exists('/var/log/upstart'):
        return True
    else:
        return False

def backup_grub():
    '''
    Create a backup of the GRUB configuration file.
    Saves a copy of grub file to BACKUP_PATH
    Input: None
    Output: Boolean (True if successful, False on error)
    '''
    os.makedirs(BACKUP_PATH, exist_ok=True)

    try:
        with open(GRUB_CONF_FILE, 'r') as grub_file:
            grub_content = grub_file.read()

        with open(BACKUP_PATH + 'grub' + BACKUP_SUFIX, 'w') as grub_backup:
            grub_backup.write(grub_content)

        print(f"Backup created successfully at {BACKUP_PATH}grub{BACKUP_SUFIX}", file=sys.stdout)
        return True
    except Exception as e:
        print(f"Error creating backup: {e}", file=sys.stderr)
        return False

def configure_grub():
    '''
    Configure GRUB to set serial console parameters.
    Updates GRUB_CMDLINE_LINUX with console settings for tty0 and ttyS0.
    Then runs 'update-grub' to apply changes.
    Input: None
    Output: Boolean (True if successful, False on error)
    '''
    try:
        with open (GRUB_CONF_FILE, 'r') as f:
            lines = f.readlines()

        new_file = ''

        for line in lines:
            if line.startswith('GRUB_CMDLINE_LINUX='):
                new_file += f'GRUB_CMDLINE_LINUX="{GRUB_CMDLINE_LINUX}"\n'
            else:
                new_file += line

        with open (GRUB_CONF_FILE, 'w') as f:
            f.write(new_file)

        subprocess.run(['update-grub'], check=True)

        print("GRUB configuration updated successfully", file=sys.stdout)
        return True

    except subprocess.CalledProcessError as e:
        print(f"Error executing command: {e}", file=sys.stderr)
        return False
    except Exception as e:
        print(f"Unexpected error: {e}", file=sys.stderr)
        return False    


def configure_tty_systemd():
    '''
    Enable and start the serial console service for ttyS0 using systemd.
    Runs 'systemctl enable' and 'systemctl start' for serial-getty@ttyS0.service.
    Input: None
    Output: Boolean (True if successful, False on error)
    '''
    try:

        # 2. Habilitar servicio serial
        subprocess.run(['systemctl', 'enable', 'serial-getty@ttyS0.service'], check=True)
        subprocess.run(['systemctl', 'start', 'serial-getty@ttyS0.service'], check=True)

        print('Serial console service configured successfully (Systemd)', file=sys.stdout)
        return True
    except subprocess.CalledProcessError as e:
        print(f'Error executing command: {e}', file=sys.stderr)
        return False
    except Exception as e:
        print(f'Unexpected error: {e}', file=sys.stderr)
        return False

def configure_tty_upstart():
    '''
    Create an Upstart configuration file for ttyS0 serial console.
    Writes a job configuration under /etc/init/ to manage getty on ttyS0.
    Input: None
    Output: Boolean (True after file creation)
    '''
    try:
        os.makedirs(UPSTART_PATH, exist_ok=True)

        with open (UPSTART_PATH + UPSTART_TTY_FILE, 'w') as f:
            f.write(
                '# ttyS0 - getty \n'\
                '# \n '\
                '# This service maintains a getty on ttyS0 from the point the system is \n' \
                '# started until it is shut down again. \n'
                'start on stopped rc RUNLEVEL=[12345]\n'
                'stop on runlevel [!12345]\n'
                'respawn\n'
                'exec /sbin/getty -L 115200 ttyS0 vt102\n'
                )

        print("Serial console configuration file created for Upstart", file=sys.stdout)
        return True

    except Exception as e:
        print(f"Error creating Upstart configuration: {e}", file=sys.stderr)
        return False

if __name__ == "__main__":

    if check_root():

        if check_systemd():
            backup_grub()
            configure_grub()
            configure_tty_systemd()

        elif check_upstart():
            backup_grub()
            configure_grub()
            configure_tty_upstart()

        else:
            print('Error: Unrecognized init system. This script is only compatible with Systemd and Upstart.', file=sys.stderr)

    else:
        print('Error: This script needs to be executed with root privileges.', file=sys.stderr)