From 61283acf2c0cc3864ffa2a4d6f6d45400822e472 Mon Sep 17 00:00:00 2001 From: Martin Kolman Date: Tue, 12 Mar 2024 18:51:03 +0100 Subject: [PATCH] Replace VNC support with GNOME remote desktop Rebuild the original TUI Ask VNC spoke to a more generic "Ask Remote Desktop" spoke, supporting RDP. Replace the Tiger VNC provided VNC support with GNOME remote desktop provided RDP support & remove Tiger VNC support. Also log a warning if the old VNC options are used & these options will now no longer have an effect. Remove module from the code so it should be easier for backporting to rhel-10. However, we should replace Vnc module with Rdp module in later commit. Resolves: RHEL-38407 (merge of commit 883d6d040f35e1a7b5706570a6e304432b1d512c) (merge of commit da6f157df1ca8040a9b6a2085857e5fb8d8bf4ea) --- anaconda.py | 4 +- pyanaconda/core/constants.py | 2 +- pyanaconda/display.py | 227 +++++++------ pyanaconda/gnome_remote_destop.py | 7 +- pyanaconda/startup_utils.py | 2 +- .../ui/tui/spokes/{askvnc.py => askrd.py} | 146 ++++++--- pyanaconda/vnc.py | 309 ------------------ .../pyanaconda_tests/test_simple_import.py | 2 +- 8 files changed, 227 insertions(+), 472 deletions(-) rename pyanaconda/ui/tui/spokes/{askvnc.py => askrd.py} (55%) delete mode 100644 pyanaconda/vnc.py diff --git a/anaconda.py b/anaconda.py index e850f8e0a25e..0a62d9d2e24f 100755 --- a/anaconda.py +++ b/anaconda.py @@ -41,7 +41,7 @@ def exitHandler(rebootData): # pylint: disable=possibly-used-before-assignment # pylint: disable=used-before-assignment if flags.use_rd: - vnc.shutdownServer() + gnome_remote_destop.shutdown_server() # pylint: disable=possibly-used-before-assignment # pylint: disable=used-before-assignment @@ -292,7 +292,7 @@ def warn_on_deprecated_options(opts, log): opts.display_mode = constants.DisplayModes.TUI opts.noninteractive = True - from pyanaconda import vnc + from pyanaconda import gnome_remote_destop from pyanaconda import kickstart # we are past the --version and --help shortcut so we can import display & # startup_utils, which import Blivet, without slowing down anything critical diff --git a/pyanaconda/core/constants.py b/pyanaconda/core/constants.py index 2f7dd28236af..1c676909411a 100644 --- a/pyanaconda/core/constants.py +++ b/pyanaconda/core/constants.py @@ -84,7 +84,7 @@ DRACUT_SHUTDOWN_EJECT = "/run/initramfs/usr/lib/dracut/hooks/shutdown/99anaconda-eject.sh" # VNC questions -USEVNC = N_("Start VNC") +USERDP = N_("Use graphical mode via Remote Desktop Protocol") USETEXT = N_("Use text mode") # Quit message diff --git a/pyanaconda/display.py b/pyanaconda/display.py index fa467c1407db..615e80fe00f9 100644 --- a/pyanaconda/display.py +++ b/pyanaconda/display.py @@ -31,13 +31,11 @@ from pyanaconda.core.process_watchers import WatchProcesses from pyanaconda import startup_utils from pyanaconda.core import util, constants, hw -from pyanaconda import vnc +from pyanaconda.gnome_remote_destop import GRDServer from pyanaconda.core.i18n import _ from pyanaconda.flags import flags -from pyanaconda.modules.common.constants.objects import USER_INTERFACE -from pyanaconda.modules.common.constants.services import NETWORK, RUNTIME -from pyanaconda.modules.common.structures.vnc import VncData -from pyanaconda.ui.tui.spokes.askvnc import AskVNCSpoke +from pyanaconda.modules.common.constants.services import NETWORK +from pyanaconda.ui.tui.spokes.askrd import AskRDSpoke, RDPAuthSpoke from pyanaconda.ui.tui import tui_quit_callback # needed for checking if the pyanaconda.ui.gui modules are available import pyanaconda.ui @@ -60,7 +58,6 @@ "Enforce text mode when installing from remote media with the inst.text boot option." # on RHEL also: "Use the customer portal download URL in ilo/drac devices for greater speed." - def start_user_systemd(): """Start the user instance of systemd. @@ -105,85 +102,104 @@ def start_spice_vd_agent(): log.info("Started spice-vdagent.") -# VNC +# RDP -def ask_vnc_question(anaconda, vnc_server, message): - """ Ask the user if TUI or GUI-over-VNC should be started. +def ask_rd_question(anaconda, grd_server, message): + """ Ask the user if TUI or GUI-over-RDP should be started. :param anaconda: instance of the Anaconda class - :param vnc_server: instance of the VNC server object + :param grd_server: instance of the GRD server object :param str message: a message to show to the user together with the question + :return: if remote desktop should be used + :rtype: bool """ App.initialize() loop = App.get_event_loop() loop.set_quit_callback(tui_quit_callback) # Get current vnc data from DBUS - ui_proxy = RUNTIME.get_proxy(USER_INTERFACE) - vnc_data = VncData.from_structure(ui_proxy.Vnc) - spoke = AskVNCSpoke(anaconda.ksdata, vnc_data, message=message) + spoke = AskRDSpoke(anaconda.ksdata, message=message) ScreenHandler.schedule_screen(spoke) App.run() - # Update vnc data from DBUS - vnc_data = VncData.from_structure(ui_proxy.Vnc) - - if vnc_data.enabled: + if spoke.use_remote_desktop: if not anaconda.gui_mode: - log.info("VNC requested via VNC question, switching Anaconda to GUI mode.") + log.info("RDP requested via RDP question, switching Anaconda to GUI mode.") anaconda.display_mode = constants.DisplayModes.GUI flags.use_rd = True - vnc_server.password = vnc_data.password.value + grd_server.rdp_username = spoke.rdp_username + grd_server.rdp_password = spoke.rdp_password + + return spoke.use_remote_desktop + +def ask_for_rd_credentials(anaconda, grd_server, username=None, password=None): + """ Ask the user to provide RDP credentials interactively. + + :param anaconda: instance of the Anaconda class + :param grd_server: instance of the GRD server object + :param str username: user set username (if any) + :param str password: user set password (if any) + """ + App.initialize() + loop = App.get_event_loop() + loop.set_quit_callback(tui_quit_callback) + spoke = RDPAuthSpoke(anaconda.ksdata, username=username, password=password) + ScreenHandler.schedule_screen(spoke) + App.run() + log.info("RDP credentials set") + anaconda.display_mode = constants.DisplayModes.GUI + flags.use_rd = True + grd_server.rdp_username = spoke._username + grd_server.rdp_password = spoke._password -def check_vnc_can_be_started(anaconda): - """Check if we can start VNC in the current environment. +def check_rd_can_be_started(anaconda): + """Check if we can start an RDP session in the current environment. - :returns: if VNC can be started and list of possible reasons - why VNC can't be started + :returns: if RDP session can be started and list of possible reasons + why the session can't be started :rtype: (boot, list) """ error_messages = [] - vnc_startup_possible = True + rd_startup_possible = True - # disable VNC over text question when not enough memory is available + # disable remote desktop over text question when not enough memory is available min_gui_ram = hw.minimal_memory_needed(with_gui=True) if blivet.util.total_memory() < min_gui_ram: - error_messages.append("Not asking for VNC because current memory (%d) < MIN_GUI_RAM (%d)" % + error_messages.append("Not asking for remote desktop session because current memory " + "(%d) < MIN_GUI_RAM (%d)" % (blivet.util.total_memory(), min_gui_ram)) - vnc_startup_possible = False - - # if running in text mode, we might sometimes skip showing the VNC question - if anaconda.tui_mode: - # disable VNC question if we were explicitly asked for text mode in kickstart - ui_proxy = RUNTIME.get_proxy(USER_INTERFACE) - if ui_proxy.DisplayModeTextKickstarted: - error_messages.append( - "Not asking for VNC because text mode was explicitly asked for in kickstart" - ) - vnc_startup_possible = False - # disable VNC question if text mode is requested and this is an automated kickstart - # installation - elif flags.automatedInstall: - error_messages.append("Not asking for VNC because of an automated install") - vnc_startup_possible = False - - # disable VNC question if we don't have network + rd_startup_possible = False + + # disable remote desktop question if text mode is requested and this is a ks install + if anaconda.tui_mode and flags.automatedInstall: + error_messages.append( + "Not asking for remote desktop session because of an automated install" + ) + rd_startup_possible = False + + # disable remote desktop question if we were explicitly asked for text in kickstart + if anaconda.display_mode == constants.DisplayModes.TUI: + error_messages.append("Not asking for remote desktop session because text mode " + "was explicitly asked for in kickstart") + rd_startup_possible = False + + # disable remote desktop question if we don't have network network_proxy = NETWORK.get_proxy() if not network_proxy.IsConnecting() and not network_proxy.Connected: error_messages.append("Not asking for VNC because we don't have a network") - vnc_startup_possible = False + rd_startup_possible = False - # disable VNC question if we don't have Xvnc - if not os.access('/usr/bin/Xvnc', os.X_OK): - error_messages.append("Not asking for VNC because we don't have Xvnc") - vnc_startup_possible = False + # disable remote desktop question if we don't have GNOME remote desktop + if not os.access('/usr/bin/grdctl', os.X_OK): + error_messages.append("Not asking for remote desktop because we don't have grdctl") + rd_startup_possible = False - return vnc_startup_possible, error_messages + return rd_startup_possible, error_messages -def do_startup_wl_actions(timeout): +def do_startup_wl_actions(timeout, headless=False, headless_resolution=None): """Start the window manager. When window manager actually connects to the X server is unknowable, but @@ -193,6 +209,9 @@ def do_startup_wl_actions(timeout): fingers crossed. Add XDG_DATA_DIRS to the environment to pull in our overridden schema files. + + :param bool headless: start a headless session (used for RDP access) + :param str headless_resolution: headless virtual monitor resolution in WxH format """ datadir = os.environ.get('ANACONDA_DATADIR', '/usr/share/anaconda') if 'XDG_DATA_DIRS' in os.environ: @@ -205,21 +224,39 @@ def do_startup_wl_actions(timeout): xdg_config_dirs = datadir + ':' + os.environ['XDG_CONFIG_DIRS'] # pylint: disable=environment-modify os.environ['XDG_CONFIG_DIRS'] = xdg_config_dirs - os.environ["XDG_SESSION_TYPE"] = "wayland" def wl_preexec(): # to set GUI subprocess SIGINT handler signal.signal(signal.SIGINT, signal.SIG_IGN) + # lets compile arguments for the run-in-new-session script argv = ["/usr/libexec/anaconda/run-in-new-session", "--user", "root", "--service", "anaconda", - "--vt", "6", "--session-type", "wayland", - "--session-class", "user", - "gnome-kiosk", "--sm-disable", "--wayland", "--no-x11", - "--wayland-display", constants.WAYLAND_SOCKET_NAME] + "--session-class", "user"] + + if headless: + # headless (remote connection) - stay on VT1 where connection info is + argv.extend(["--vt", "1"]) + else: + # local display - switch to VT6 & show GUI there + argv.extend(["--vt", "6"]) + + # add the generic GNOME Kiosk invocation + argv.extend(["gnome-kiosk", "--sm-disable", + "--wayland", "--no-x11", + "--wayland-display", constants.WAYLAND_SOCKET_NAME]) + + # remote access needs gnome-kiosk to start in headless mode & + # configure a virtual monitor + if headless: + # check virtual monitor resolution has been set + if headless_resolution is None: + # use default value + headless_resolution = "1280x1024" + argv.extend(["--headless", "--virtual-monitor", headless_resolution]) childproc = util.startProgram(argv, env_add={'XDG_DATA_DIRS': xdg_data_dirs}, preexec_fn=wl_preexec) @@ -287,46 +324,22 @@ def setup_display(anaconda, options): log.warning("invalid inst.xtimeout option value: %s", options.xtimeout) xtimeout = constants.X_TIMEOUT - vnc_server = vnc.VncServer() # The vnc Server object. - vnc_server.anaconda = anaconda - vnc_server.timeout = xtimeout + grd_server = GRDServer(anaconda) # The RDP server object + rdp_credentials_sufficient = False - if options.vnc: + if options.rdp_enabled: flags.use_rd = True if not anaconda.gui_mode: - log.info("VNC requested via boot/CLI option, switching Anaconda to GUI mode.") + log.info("RDP requested via boot/CLI option, switching Anaconda to GUI mode.") anaconda.display_mode = constants.DisplayModes.GUI - vnc_server.password = options.vncpassword - - # Only consider vncconnect when vnc is a param - if options.vncconnect: - cargs = options.vncconnect.split(":") - vnc_server.vncconnecthost = cargs[0] - if len(cargs) > 1 and len(cargs[1]) > 0: - if len(cargs[1]) > 0: - vnc_server.vncconnectport = cargs[1] + grd_server.rdp_username = options.rdp_username + grd_server.rdp_password = options.rdp_password + # note if we have both set + rdp_credentials_sufficient = options.rdp_username and options.rdp_password if options.xdriver: write_xdriver(options.xdriver, root="/") - ui_proxy = RUNTIME.get_proxy(USER_INTERFACE) - vnc_data = VncData.from_structure(ui_proxy.Vnc) - - if vnc_data.enabled: - flags.use_rd = True - if not anaconda.gui_mode: - log.info("VNC requested via kickstart, switching Anaconda to GUI mode.") - anaconda.display_mode = constants.DisplayModes.GUI - - if vnc_server.password == "": - vnc_server.password = vnc_data.password.value - - if vnc_server.vncconnecthost == "": - vnc_server.vncconnecthost = vnc_data.host - - if vnc_server.vncconnectport == "": - vnc_server.vncconnectport = vnc_data.port - # check if GUI without WebUI if anaconda.gui_mode and not anaconda.is_webui_supported: mods = (tup[1] for tup in pkgutil.iter_modules(pyanaconda.ui.__path__, "pyanaconda.ui.")) @@ -336,23 +349,29 @@ def setup_display(anaconda, options): flags.use_rd = False flags.rd_question = False - # check if VNC can be started - vnc_can_be_started, vnc_error_messages = check_vnc_can_be_started(anaconda) - if not vnc_can_be_started: - # VNC can't be started - disable the VNC question and log - # all the errors that prevented VNC from being started + # check if remote desktop mode can be started + rd_can_be_started, rd_error_messages = check_rd_can_be_started(anaconda) + + if rd_can_be_started: + # if remote desktop can be started & only inst.rdp + # or inst.rdp and insufficient credentials are provided + # via boot options, ask interactively. + if options.rdp_enabled and not rdp_credentials_sufficient: + ask_for_rd_credentials(anaconda, grd_server, options.rdp_username, options.rdp_password) + else: + # RDP can't be started - disable the RDP question and log + # all the errors that prevented RDP from being started flags.rd_question = False - for error_message in vnc_error_messages: + for error_message in rd_error_messages: stdout_log.warning(error_message) if anaconda.tui_mode and flags.rd_question: - # we prefer vnc over text mode, so ask about that + # we prefer remote desktop over text mode, so ask about that message = _("Text mode provides a limited set of installation " "options. It does not offer custom partitioning for " "full control over the disk layout. Would you like " - "to use VNC mode instead?") - ask_vnc_question(anaconda, vnc_server, message) - if not vnc_data.enabled: + "to use remote graphical access via the RDP protocol instead?") + if not ask_rd_question(anaconda, grd_server, message): # user has explicitly specified text mode flags.rd_question = False @@ -394,17 +413,17 @@ def on_mutter_ready(observer): mutter_display = MutterDisplay() mutter_display.on_service_ready(on_mutter_ready) - if anaconda.tui_mode and anaconda.gui_startup_failed and \ - flags.rd_question and not vnc_data.enabled: + if anaconda.tui_mode and anaconda.gui_startup_failed and flags.rd_question: + message = _("X was unable to start on your machine. Would you like to start VNC to connect to " "this computer from another computer and perform a graphical installation or continue " "with a text mode installation?") - ask_vnc_question(anaconda, vnc_server, message) + ask_rd_question(anaconda, grd_server, message) - # if they want us to use VNC do that now + # if they want us to use RDP do that now if anaconda.gui_mode and flags.use_rd: - vnc_server.startServer() - do_startup_wl_actions(xtimeout) + do_startup_wl_actions(xtimeout, headless=True, headless_resolution=options.runres) + grd_server.start_grd_rdp() # with X running we can initialize the UI interface anaconda.initialize_interface() diff --git a/pyanaconda/gnome_remote_destop.py b/pyanaconda/gnome_remote_destop.py index 338ba85b1cf9..3813a1edd6a4 100644 --- a/pyanaconda/gnome_remote_destop.py +++ b/pyanaconda/gnome_remote_destop.py @@ -20,17 +20,16 @@ import os import sys import time +import socket from pyanaconda import network from pyanaconda.core import util from pyanaconda.core.util import execWithCapture, startProgram -import socket from pyanaconda.core.i18n import _ -from pyanaconda.anaconda_loggers import get_stdout_logger -stdoutLog = get_stdout_logger() +from pyanaconda.anaconda_loggers import get_stdout_logger, get_module_logger -from pyanaconda.anaconda_loggers import get_module_logger +stdoutLog = get_stdout_logger() log = get_module_logger(__name__) OPENSSL_BINARY_PATH = "/usr/bin/openssl" diff --git a/pyanaconda/startup_utils.py b/pyanaconda/startup_utils.py index 627fe28b8a2c..3b39000c505c 100644 --- a/pyanaconda/startup_utils.py +++ b/pyanaconda/startup_utils.py @@ -255,7 +255,7 @@ def prompt_for_ssh(options): if options.ksfile: return False - if options.vnc: + if options.rdp: return False # Do some work here to get the ip addr / hostname to pass diff --git a/pyanaconda/ui/tui/spokes/askvnc.py b/pyanaconda/ui/tui/spokes/askrd.py similarity index 55% rename from pyanaconda/ui/tui/spokes/askvnc.py rename to pyanaconda/ui/tui/spokes/askrd.py index 1cd4dcf3925d..4c6428b5dba1 100644 --- a/pyanaconda/ui/tui/spokes/askvnc.py +++ b/pyanaconda/ui/tui/spokes/askrd.py @@ -1,6 +1,8 @@ -# Ask vnc text spoke +# Ask Remote Desktop text spoke # -# Copyright (C) 2012 Red Hat, Inc. +# Asks the user if a text mode or remote desktop based access should be used. +# +# Copyright (C) 2024 Red Hat, Inc. # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions of @@ -19,11 +21,8 @@ import sys from pyanaconda.core.configuration.anaconda import conf -from pyanaconda.modules.common.constants.objects import USER_INTERFACE -from pyanaconda.modules.common.constants.services import RUNTIME -from pyanaconda.modules.common.structures.vnc import VncData from pyanaconda.ui.tui.spokes import NormalTUISpoke -from pyanaconda.core.constants import USEVNC, USETEXT, QUIT_MESSAGE +from pyanaconda.core.constants import USERDP, USETEXT, QUIT_MESSAGE from pyanaconda.core.i18n import N_, _ from pyanaconda.ui.tui import exception_msg_handler from pyanaconda.core.util import execWithRedirect, ipmi_abort @@ -44,18 +43,17 @@ def exception_msg_handler_and_exit(signal, data): sys.exit(1) -class AskVNCSpoke(NormalTUISpoke): +class AskRDSpoke(NormalTUISpoke): """ - .. inheritance-diagram:: AskVNCSpoke + .. inheritance-diagram:: AskRDPSpoke :parts: 3 """ - title = N_("VNC") + title = N_("RDP") # This spoke is kinda standalone, not meant to be used with a hub # We pass in some fake data just to make our parents happy - def __init__(self, data, vnc_data, storage=None, payload=None, message=""): + def __init__(self, data, storage=None, payload=None, message=""): super().__init__(data, storage, payload) - self.vnc_data = vnc_data self.input_required = True self.initialize_start() self._container = None @@ -66,9 +64,26 @@ def __init__(self, data, vnc_data, storage=None, payload=None, message=""): loop = App.get_event_loop() loop.register_signal_handler(ExceptionSignal, exception_msg_handler_and_exit) self._message = message + self._rdp_username = "" + self._rdp_password = "" self._use_rd = False self.initialize_done() + @property + def use_remote_desktop(self): + """Should a remote desktop solution be used instead of text mode ?""" + return self._use_rd + + @property + def rdp_username(self): + """User provided RDP user name (if any).""" + return self._rdp_username + + @property + def rdp_password(self): + """User provided RDP password (if any).""" + return self._rdp_password + @property def indirect(self): return True @@ -81,23 +96,25 @@ def refresh(self, args=None): self._container = ListColumnContainer(1, spacing=1) # choices are - # USE VNC - self._container.add(TextWidget(_(USEVNC)), self._use_vnc_callback) + # USE RDP + self._container.add(TextWidget(_(USERDP)), self._use_rdp_callback) # USE TEXT self._container.add(TextWidget(_(USETEXT)), self._use_text_callback) self.window.add_with_separator(self._container) - def _use_vnc_callback(self, data): + def _use_rdp_callback(self, data): self._use_rd = True - new_spoke = VNCPassSpoke(self.data, self.storage, self.payload, self.vnc_data) - ScreenHandler.push_screen_modal(new_spoke) + new_rdp_spoke = RDPAuthSpoke(self.data) + ScreenHandler.push_screen_modal(new_rdp_spoke) + self._rdp_username = new_rdp_spoke._username + self._rdp_password = new_rdp_spoke._password def _use_text_callback(self, data): self._use_rd = False def input(self, args, key): - """Override input so that we can launch the VNC password spoke""" + """Override input so that we can launch the RDP user name & password spoke""" if self._container.process_user_input(key): self.apply() return InputState.PROCESSED_AND_CLOSE @@ -115,28 +132,28 @@ def input(self, args, key): return super().input(args, key) def apply(self): - self.vnc_data.enabled = self._use_rd - ui_proxy = RUNTIME.get_proxy(USER_INTERFACE) - struct_vnc = VncData.to_structure(self.vnc_data) - ui_proxy.Vnc = struct_vnc + pass -class VNCPassSpoke(NormalTUISpoke): +class RDPAuthSpoke(NormalTUISpoke): """ .. inheritance-diagram:: VNCPassSpoke :parts: 3 """ - def __init__(self, data, storage, payload, message=None, vnc_data=None): - super().__init__(data, storage, payload) - self.vnc_data = vnc_data - self.title = N_("VNC Password") - self._password = "" - if message: - self._message = message + def __init__(self, data, username=None, password=None): + super().__init__(data, storage=None, payload=None) + self.title = N_("RDP User name & Password") + + if username is not None: + self._username = username + else: + self._username = "" + + if password is not None: + self._password = password else: - self._message = _("Please provide VNC password (must be six to eight characters long).\n" - "You will have to type it twice. Leave blank for no password") + self._password = "" @property def indirect(self): @@ -144,27 +161,59 @@ def indirect(self): @property def completed(self): - return True # We're always complete + return True # We're always complete def refresh(self, args=None): super().refresh(args) - self.window.add_with_separator(TextWidget(self._message)) + self.window.add_with_separator(TextWidget(self.message)) + + @property + def message(self): + text = "" + if not self._username and not self._password: + text = _("Please provide RDP user name & password.") + elif self._username: + text = _("Please provide RDP password.") + else: + text = _("Please provide RDP user name.") + + # if we want the password, add a note about typing it twice + if not self._password: + text = text + "\n" + _("You will have to type the password twice.") + + return text def prompt(self, args=None): """Override prompt as password typing is special.""" - p1 = self.get_user_input(_("Password: "), True) - p2 = self.get_user_input(_("Password (confirm): "), True) - - if p1 != p2: - self._print_error_and_redraw(_("Passwords do not match!")) - elif 0 < len(p1) < 6: - self._print_error_and_redraw((_("The password must be at least " - "six characters long."))) - elif len(p1) > 8: - self._print_error_and_redraw(_("The password cannot be more than " - "eight characters long.")) - else: - self._password = p1 + # first make sure username is set + if not self._username: + username = self.get_user_input(_("User name: "), False) + if username: + self._username = username + else: + self._print_error_and_redraw(_("User name not set!")) + return None + + # next try to get the password + if not self._password: + p1 = self.get_user_input(_("Password: "), True) + p2 = self.get_user_input(_("Password (confirm): "), True) + + if p1 != p2: + self._print_error_and_redraw(_("Passwords do not match!")) + return None + elif not p1: + self._print_error_and_redraw((_("The password must not be empty."))) + return None + elif 0 < len(p1) < 6: + self._print_error_and_redraw((_("The password must be at least " + "six characters long."))) + return None + else: + self._password = p1 + + # do we finally have everything ? + if self._username and self._password: self.apply() self.close() @@ -176,7 +225,4 @@ def _print_error_and_redraw(self, msg): self.redraw() def apply(self): - self.vnc_data.password.set_secret(self._password) - ui_proxy = RUNTIME.get_proxy(USER_INTERFACE) - struct_vnc = VncData.to_structure(self.vnc_data) - ui_proxy.Vnc = struct_vnc + pass diff --git a/pyanaconda/vnc.py b/pyanaconda/vnc.py deleted file mode 100644 index dffae2cf480d..000000000000 --- a/pyanaconda/vnc.py +++ /dev/null @@ -1,309 +0,0 @@ -# -# vnc.py: VNC related installer functionality -# -# Copyright (C) 2004, 2007 Red Hat, Inc. All rights reserved. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# - -import os -import sys -import time -from pyanaconda import network -from pyanaconda.core import util, constants -from pyanaconda.core.product import get_product_name, get_product_version -import socket -import subprocess - -from pyanaconda.core.i18n import _, P_ -from pyanaconda.modules.common.constants.objects import USER_INTERFACE -from pyanaconda.modules.common.constants.services import RUNTIME -from pyanaconda.modules.common.structures.vnc import VncData -from pyanaconda.ui.tui import tui_quit_callback -from pyanaconda.ui.tui.spokes.askvnc import VNCPassSpoke - -from simpleline import App -from simpleline.render.screen_handler import ScreenHandler - -from pyanaconda.anaconda_loggers import get_stdout_logger -stdoutLog = get_stdout_logger() - -from pyanaconda.anaconda_loggers import get_module_logger -log = get_module_logger(__name__) - -XVNC_BINARY_NAME = "Xvnc" - - -def shutdownServer(): - """Try to shutdown any running XVNC server - - Why is this function on the module level and not in the VncServer class ? - - As the server needs to be killed from the exit handler, it would have - to somehow get to the VncServer instance. Like this, it can just kill - it by calling a function of the vnc module. - """ - try: - util.execWithCapture("killall", [XVNC_BINARY_NAME], do_preexec=False) - log.info("The XVNC server has been shut down.") - except OSError as e: - log.error("Shutdown of the XVNC server failed with exception:\n%s", e) - - -class VncServer(object): - - def __init__(self, root="/", ip=None, name=None, - password="", vncconnecthost="", - vncconnectport="", log_file="/tmp/vncserver.log", - pw_file="/tmp/vncpassword", timeout=constants.X_TIMEOUT): - self.root = root - self.ip = ip - self.name = name - self.password = password - self.vncconnecthost = vncconnecthost - self.vncconnectport = vncconnectport - self.log_file = log_file - self.pw_file = pw_file - self.timeout = timeout - self.connxinfo = None - self.anaconda = None - self.log = get_stdout_logger() - - self.desktop = _("%(productName)s %(productVersion)s installation")\ - % {'productName': get_product_name(), - 'productVersion': get_product_version()} - - def setVNCPassword(self): - """Set the vnc server password. Output to file. """ - password_string = "%s\n" % self.password - - # the -f option makes sure vncpasswd does not ask for the password again - proc = util.startProgram( - ["vncpasswd", "-f"], - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE - ) - - out, err = proc.communicate(password_string.encode("utf-8")) - - if proc.returncode != 0: - log.error("vncpasswd has failed with %d: %s", proc.returncode, err.decode("utf-8")) - raise OSError("Unable to set the VNC password.") - - with open(self.pw_file, "wb") as pw_file: - pw_file.write(out) - - def initialize(self): - """Here is were all the relative vars get initialized. """ - - # Network may be slow. Try for 5 seconds - tries = 5 - while tries: - self.ip = network.get_first_ip_address() - if self.ip: - break - time.sleep(1) - tries -= 1 - - if not self.ip: - return - - if self.ip.find(':') != -1: - ipstr = "[%s]" % (self.ip,) - else: - ipstr = self.ip - - try: - hinfo = socket.gethostbyaddr(self.ip) - if len(hinfo) == 3: - # Consider as coming from a valid DNS record only if single IP is returned - if len(hinfo[2]) == 1: - self.name = hinfo[0] - except socket.herror as e: - log.debug("Exception caught trying to get host name of %s: %s", ipstr, e) - - if self.name is not None and not self.name.startswith('localhost'): - self.connxinfo = "%s:%s (%s:%s)" % (socket.getfqdn(name=self.name), - constants.X_DISPLAY_NUMBER, - ipstr, - constants.X_DISPLAY_NUMBER) - host = self.name - elif ipstr is not None: - self.connxinfo = "%s:%s" % (ipstr, constants.X_DISPLAY_NUMBER) - host = ipstr - else: - self.connxinfo = None - host = "" - - # figure out product info - if host: - self.desktop = _("%(productName)s %(productVersion)s installation " - "on host %(name)s") \ - % {'productName': get_product_name(), - 'productVersion': get_product_version(), - 'name': host} - - def openlogfile(self): - try: - fd = os.open(self.log_file, os.O_RDWR | os.O_CREAT) - except OSError as e: - sys.stderr.write("error opening %s: %s\n", (self.log_file, e)) - fd = None - - return fd - - def connectToView(self): - """Attempt to connect to self.vncconnecthost""" - - maxTries = 10 - self.log.info(_("Attempting to connect to vnc client on host %s..."), self.vncconnecthost) - - if self.vncconnectport != "": - hostarg = self.vncconnecthost + ":" + self.vncconnectport - else: - hostarg = self.vncconnecthost - - vncconfigcommand = [self.root + "/usr/bin/vncconfig", "-display", ":%s" % constants.X_DISPLAY_NUMBER, "-connect", hostarg] - - for _i in range(maxTries): - vncconfp = util.startProgram(vncconfigcommand, stdout=subprocess.PIPE, stderr=subprocess.PIPE) # vncconfig process - err = vncconfp.communicate()[1].decode("utf-8") - - if err == '': - self.log.info(_("Connected!")) - return True - elif err.startswith("connecting") and err.endswith("failed\n"): - self.log.info(_("Will try to connect again in 15 seconds...")) - time.sleep(15) - continue - else: - log.critical(err) - util.ipmi_abort(scripts=self.anaconda.ksdata.scripts) - sys.exit(1) - self.log.error(P_("Giving up attempting to connect after %d try!\n", - "Giving up attempting to connect after %d tries!\n", - maxTries), maxTries) - return False - - def startVncConfig(self): - """Attempt to start vncconfig""" - - self.log.info(_("Attempting to start vncconfig")) - - vncconfigcommand = [self.root + "/usr/bin/vncconfig", "-nowin", "-display", ":%s" % constants.X_DISPLAY_NUMBER] - - # Use startProgram to run vncconfig in the background - util.startProgram(vncconfigcommand, stdout=self.openlogfile(), stderr=subprocess.STDOUT) - - def VNCListen(self): - """Put the server in listening mode. - - We dont really have to do anything for the server to listen :) - """ - if self.connxinfo is not None: - self.log.info(_("Please manually connect your vnc client to %s to begin the install."), self.connxinfo) - else: - self.log.info(_("Please manually connect your vnc client to IP-ADDRESS:%s " - "to begin the install. Switch to the shell (Ctrl-B 2) and " - "run 'ip addr' to find the IP-ADDRESS."), constants.X_DISPLAY_NUMBER) - - def startServer(self): - self.log.info(_("Starting VNC...")) - network.wait_for_connectivity() - - # Lets call it from here for now. - try: - self.initialize() - except (socket.herror, ValueError) as e: - stdoutLog.critical("Could not initialize the VNC server: %s", e) - util.ipmi_abort(scripts=self.anaconda.ksdata.scripts) - sys.exit(1) - - if self.password and (len(self.password) < 6 or len(self.password) > 8): - self.changeVNCPasswdWindow() - - if not self.password: - SecurityTypes = "None" - rfbauth = "0" - else: - SecurityTypes = "VncAuth" - rfbauth = self.pw_file - # Create the password file. - self.setVNCPassword() - - # Lets start the xvnc. - xvnccommand = [XVNC_BINARY_NAME, ":%s" % constants.X_DISPLAY_NUMBER, - "-depth", "24", "-br", - "IdleTimeout=0", "-auth", "/dev/null", "-once", - "DisconnectClients=false", "desktop=%s" % (self.desktop,), - "SecurityTypes=%s" % SecurityTypes, "rfbauth=%s" % rfbauth] - - try: - util.startX(xvnccommand, output_redirect=self.openlogfile(), timeout=self.timeout) - except OSError: - stdoutLog.critical("Could not start the VNC server. Aborting.") - util.ipmi_abort(scripts=self.anaconda.ksdata.scripts) - sys.exit(1) - - self.log.info(_("The VNC server is now running.")) - - # Lets tell the user what we are going to do. - if self.vncconnecthost != "": - self.log.warning(_("\n\nYou chose to connect to a listening vncviewer. \n" - "This does not require a password to be set. If you \n" - "set a password, it will be used in case the connection \n" - "to the vncviewer is unsuccessful\n\n")) - elif self.password == "": - self.log.warning(_("\n\nWARNING!!! VNC server running with NO PASSWORD!\n" - "You can use the inst.vncpassword=PASSWORD boot option\n" - "if you would like to secure the server.\n\n")) - elif self.password != "": - self.log.warning(_("\n\nYou chose to execute vnc with a password. \n\n")) - else: - self.log.warning(_("\n\nUnknown Error. Aborting. \n\n")) - util.ipmi_abort(scripts=self.anaconda.ksdata.scripts) - sys.exit(1) - - # Lets try to configure the vnc server to whatever the user specified - if self.vncconnecthost != "": - connected = self.connectToView() - if not connected: - self.VNCListen() - else: - self.VNCListen() - - # Start vncconfig for copy/paste - self.startVncConfig() - - def changeVNCPasswdWindow(self): - """ Change the password to a sane parameter. - - We ask user to input a password that (len(password) > 6 - and len(password) <= 8) or password == ''. - """ - - message = _("VNC password must be six to eight characters long.\n" - "Please enter a new one, or leave blank for no password.") - App.initialize() - loop = App.get_event_loop() - loop.set_quit_callback(tui_quit_callback) - ui_proxy = RUNTIME.get_proxy(USER_INTERFACE) - vnc_data = VncData.from_structure(ui_proxy.Vnc) - spoke = VNCPassSpoke(self.anaconda.ksdata, None, None, message, vnc_data) - ScreenHandler.schedule_screen(spoke) - App.run() - - vnc_data = VncData.from_structure(ui_proxy.Vnc) - self.password = vnc_data.password diff --git a/tests/unit_tests/pyanaconda_tests/test_simple_import.py b/tests/unit_tests/pyanaconda_tests/test_simple_import.py index 298f7e148463..27b824ead79b 100644 --- a/tests/unit_tests/pyanaconda_tests/test_simple_import.py +++ b/tests/unit_tests/pyanaconda_tests/test_simple_import.py @@ -67,7 +67,7 @@ def test_import_pyanaconda(self): "pyanaconda.modules.storage.checker.utils", "pyanaconda.ui.categories", "pyanaconda.ui.gui.spokes.lib.cart", - "pyanaconda.ui.tui.spokes.askvnc", + "pyanaconda.ui.tui.spokes.askrd", "pyanaconda.rescue" ], [ "pyanaconda.modules.storage.partitioning.blivet.blivet_handler",