Skip to content

Commit

Permalink
Implement support for running on Wayland through Weston
Browse files Browse the repository at this point in the history
This implements rudimentary support for running Anaconda on Wayland
using the Weston compositor through XWayland. This support also
includes simple auth-less VNC remote access, though authenticated
VNC support requires extra effort since Weston's authenticated
VNC support uses TLS certificates and we do not have a good way
to plumb that through right now (more info in weston-vnc(7)).
  • Loading branch information
Conan-Kudo committed Jan 20, 2024
1 parent ae3889c commit 8f308d2
Show file tree
Hide file tree
Showing 14 changed files with 908 additions and 366 deletions.
21 changes: 17 additions & 4 deletions anaconda.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,22 @@ def setup_environment():
if "LD_PRELOAD" in os.environ:
del os.environ["LD_PRELOAD"]

# Required for Wayland compositors
if os.path.isdir("/tmp/anaconda-xdgrundir"):
os.environ["XDG_RUNTIME_DIR"] = "/tmp/anaconda-xdgrundir"

# Go ahead and set $WAYLAND_DISPLAY whether we're going to use X or not
if "WAYLAND_DISPLAY" in os.environ:
flags.preexisting_wayland = True
else:
os.environ["WAYLAND_DISPLAY"] = constants.WAYLAND_SOCKET_NAME

# Go ahead and set $DISPLAY whether we're going to use X or not
# only if X11 is being used
from pyanaconda.core.kernel import kernel_arguments
if "DISPLAY" in os.environ:
flags.preexisting_x11 = True
else:
elif "x11" in kernel_arguments and os.path.isfile("/usr/bin/Xorg"):
os.environ["DISPLAY"] = ":%s" % constants.X_DISPLAY_NUMBER

# We mostly don't run from bash, so it won't load the file for us, and libreport will then
Expand Down Expand Up @@ -296,10 +308,11 @@ def setup_environment():
except pid.PidFileError as e:
log.error("Unable to create %s, exiting", pidfile.filename)

# If we had a $DISPLAY at start and zenity is available, we may be
# running in a live environment and we can display an error dialog.
# If we had a Wayland/X11 display at start and zenity is available,
# we may be running in a live environment and we can display an error dialog.
# Otherwise just print an error.
if flags.preexisting_x11 and os.access("/usr/bin/zenity", os.X_OK):
preexisting_graphics = flags.preexisting_wayland or flags.preexisting_x11
if preexisting_graphics and os.access("/usr/bin/zenity", os.X_OK):
# The module-level _() calls are ok here because the language may
# be set from the live environment in this case, and anaconda's
# language setup hasn't happened yet.
Expand Down
8 changes: 7 additions & 1 deletion anaconda.spec.in
Original file line number Diff line number Diff line change
Expand Up @@ -248,15 +248,20 @@ Requires: zram-generator
# needed for proper driver disk support - if RPMs must be installed, a repo is needed
Requires: createrepo_c
# Display stuff moved from lorax templates
## For Wayland
Requires: weston
Requires: xorg-x11-server-Xwayland
## For X11
Requires: xorg-x11-drivers
Requires: xorg-x11-server-Xorg
Requires: xrandr
Requires: gnome-kiosk
## Common stuff
Requires: xrdb
Requires: dbus-x11
Requires: gsettings-desktop-schemas
Requires: nm-connection-editor
Requires: librsvg2
Requires: gnome-kiosk
Requires: brltty
# dependencies for rpm-ostree payload module
Requires: rpm-ostree >= %{rpmostreever}
Expand Down Expand Up @@ -394,6 +399,7 @@ rm -rf \
%{_sbindir}/anaconda
%{_sbindir}/handle-sshpw
%{_datadir}/anaconda
%{_sysconfdir}/pam.d/anaconda
%{_prefix}/libexec/anaconda
%exclude %{_datadir}/anaconda/gnome
%exclude %{_datadir}/anaconda/pixmaps
Expand Down
1 change: 1 addition & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ AC_CONFIG_FILES([Makefile
data/liveinst/gnome/Makefile
data/systemd/Makefile
data/dbus/Makefile
data/pam/Makefile
data/window-manager/Makefile
data/window-manager/config/Makefile
po/Makefile
Expand Down
2 changes: 1 addition & 1 deletion data/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

SUBDIRS = command-stubs liveinst systemd pixmaps window-manager dbus conf.d profile.d
SUBDIRS = command-stubs liveinst systemd pixmaps window-manager dbus conf.d profile.d pam

CLEANFILES = *~

Expand Down
21 changes: 21 additions & 0 deletions data/pam/Makefile.am
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copyright (C) 2024 Neal Gompa.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

CLEANFILES = *~

pamdir = $(sysconfdir)/pam.d
dist_pam_DATA = anaconda

MAINTAINERCLEANFILES = Makefile.in
8 changes: 8 additions & 0 deletions data/pam/anaconda
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#%PAM-1.0
auth sufficient pam_permit.so
account sufficient pam_permit.so
password sufficient pam_permit.so
session required pam_loginuid.so
-session optional pam_keyinit.so revoke
-session optional pam_limits.so
session required pam_systemd.so
11 changes: 10 additions & 1 deletion data/systemd/anaconda.service
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ Wants=anaconda-noshell.service

[Service]
Type=forking
Environment=HOME=/root MALLOC_CHECK_=2 MALLOC_PERTURB_=204 PATH=/usr/bin:/bin:/sbin:/usr/sbin:/mnt/sysimage/bin:/mnt/sysimage/usr/bin:/mnt/sysimage/usr/sbin:/mnt/sysimage/sbin LANG=en_US.UTF-8 GDK_BACKEND=x11 XDG_RUNTIME_DIR=/tmp GIO_USE_VFS=local
Environment=HOME=/root MALLOC_CHECK_=2 MALLOC_PERTURB_=204 PATH=/usr/bin:/bin:/sbin:/usr/sbin:/mnt/sysimage/bin:/mnt/sysimage/usr/bin:/mnt/sysimage/usr/sbin:/mnt/sysimage/sbin LANG=en_US.UTF-8 GDK_BACKEND=x11 XDG_RUNTIME_DIR=/tmp/anaconda-xdgrundir GIO_USE_VFS=local
WorkingDirectory=/root
User=root
Group=root
ExecStartPre=/usr/bin/mkdir -p /tmp/anaconda-xdgrundir
ExecStartPre=/usr/bin/chmod 700 /tmp/anaconda-xdgrundir
ExecStart=/usr/bin/tmux -u -f /usr/share/anaconda/tmux.conf start
PAMName=anaconda
TTYPath=/dev/tty6
TTYReset=yes
TTYVHangup=yes
TTYVTDisallocate=yes
17 changes: 17 additions & 0 deletions pyanaconda/core/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,9 @@ class SecretStatus(Enum):
IPMI_ABORTED = 0x9 # installation finished unsuccessfully, due to some non-exn error
IPMI_FAILED = 0xA # installation hit an exception

# Wayland socket name to use
WAYLAND_SOCKET_NAME = "wl-sysinstall-0"

# X display number to use
X_DISPLAY_NUMBER = 1

Expand Down Expand Up @@ -315,13 +318,27 @@ class DisplayModes(Enum):
False: "noninteractive"
}

# Weston configuration
WESTON_CONFIG = {
"core": {
"shell": "kiosk",
"xwayland": "true"
}
}

# Loggers
LOGGER_ANACONDA_ROOT = "anaconda"
LOGGER_MAIN = "anaconda.main"
LOGGER_STDOUT = "anaconda.stdout"
LOGGER_PROGRAM = "program"
LOGGER_SIMPLELINE = "simpleline"

# Wayland display vars file
WAYLAND_DISPLAY_VARS_FILE = "/tmp/anaconda-wayland-display-vars"

# Timeout for starting Wayland
WAYLAND_TIMEOUT = 60

# Timeout for starting X
X_TIMEOUT = 60

Expand Down
95 changes: 93 additions & 2 deletions pyanaconda/core/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,17 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#

import configparser
import os
import os.path
import pathlib
import subprocess
# Used for ascii_lowercase, ascii_uppercase constants
import tempfile
import time
import re
import signal
import stat
import sys
import types
import inspect
Expand All @@ -39,8 +43,9 @@
from pyanaconda.core.configuration.anaconda import conf
from pyanaconda.core.path import make_directories, open_with_perm, join_paths
from pyanaconda.core.process_watchers import WatchProcesses
from pyanaconda.core.constants import DRACUT_SHUTDOWN_EJECT, \
IPMI_ABORTED, X_TIMEOUT, PACKAGES_LIST_FILE
from pyanaconda.core.constants import DRACUT_SHUTDOWN_EJECT, IPMI_ABORTED, \
WAYLAND_SOCKET_NAME, WAYLAND_DISPLAY_VARS_FILE, WAYLAND_TIMEOUT, \
WESTON_CONFIG, X_TIMEOUT, PACKAGES_LIST_FILE
from pyanaconda.core.live_user import get_live_user
from pyanaconda.errors import RemovedModuleError

Expand Down Expand Up @@ -165,6 +170,92 @@ def preexec():
return partsubp(preexec_fn=preexec)


class WaylandStatus:
"""Status of Wayland launch.
Values of an instance can be modified from the handler functions.
"""
def __init__(self):
self.started = False
self.timed_out = False

def needs_waiting(self):
return not (self.started or self.timed_out)


def startWl(weston_config=WESTON_CONFIG, output_redirect=None, timeout=WAYLAND_TIMEOUT):
""" Start Weston for Wayland and return once Weston is ready to accept connections.
We can identify whether Weston is ready by testing if
the Wayland socket is open yet. Once it is, we can return success.
:param weston_config: The weston.ini(5) configuration to use, as a dictionary
:param output_redirect: file or file descriptor to redirect stdout and stderr to
:param timeout: Number of seconds to timing out.
"""
wl_status = WaylandStatus()

# Create the wayland vars getenv script file for Weston
wl_getenv_script_file = tempfile.NamedTemporaryFile(mode="w",
suffix="-wl-weston-getenv-sh",
delete=False)
wl_getenv_script = f"""#!/bin/sh
rm -f {WAYLAND_DISPLAY_VARS_FILE}
echo "[wayland_vars]" >> {WAYLAND_DISPLAY_VARS_FILE}
echo "WAYLAND_DISPLAY=$WAYLAND_DISPLAY" >> {WAYLAND_DISPLAY_VARS_FILE}
echo "DISPLAY=$DISPLAY" >> {WAYLAND_DISPLAY_VARS_FILE}
exit 0
"""
wl_getenv_script_file.write(wl_getenv_script)
wl_getenv_script_file.close()

weston_autolaunch_config = {"autolaunch":{"path":wl_getenv_script_file.name}}

os.chmod(wl_getenv_script_file.name,
os.stat(wl_getenv_script_file.name).st_mode | stat.S_IEXEC)

# Create the config file for Weston
weston_config_file = tempfile.NamedTemporaryFile(mode="w",
suffix="-wl-weston-sysinstall-ini",
delete=False)
weston_config_ini = configparser.ConfigParser()
for section, options in (weston_config | weston_autolaunch_config).items():
weston_config_ini.add_section(section)
for key, value in options.items():
weston_config_ini.set(section, key, str(value))
weston_config_ini.write(weston_config_file, space_around_delimiters=False)
weston_config_file.close()

# Determine whether to use drm or vnc backend
weston_backend = "drm"
if "vnc" in weston_config:
weston_backend = "vnc"

log.debug("Starting Weston.")
argv = ["weston", f"--backend={weston_backend}",
f"--config={weston_config_file.name}", "--log=/tmp/weston.log",
f"--socket={WAYLAND_SOCKET_NAME}"]

childproc = startProgram(argv, stdout=output_redirect, stderr=output_redirect)
WatchProcesses.watch_process(childproc, argv[0])

for _ in range(0, timeout):
try:
xdg_runtime_dir = os.getenv("XDG_RUNTIME_DIR")
pathlib.Path(xdg_runtime_dir, WAYLAND_SOCKET_NAME).resolve(strict=True)
wl_status.started = True
return wl_status.started
except Exception:
if wl_status.needs_waiting():
time.sleep(1)
wl_status.timed_out = True
WatchProcesses.unwatch_process(childproc)
childproc.terminate()
log.debug("Exception handler test suspended to prevent accidental activation by "
"delayed Weston start.")
raise TimeoutError("Timeout trying to start %s" % argv[0])


class X11Status:
"""Status of Xorg launch.
Expand Down
Loading

0 comments on commit 8f308d2

Please sign in to comment.