Merge branch 'autoterminal' into 'main'

implement autoterminal: automatically started scripts that take over a virtual terminal for user interaction

See merge request systemrescue/systemrescue-sources!196
This commit is contained in:
Gerd v. Egidy 2022-05-21 20:21:03 +00:00
commit ae8a15c2b2
5 changed files with 195 additions and 0 deletions

View file

@ -8,6 +8,29 @@ import glob
import os
import sys
import re
import tempfile
# pythons os.symlink bails when a file already exists, this function also handles overwrites
def symlink_overwrite(target, link_file):
link_dir = os.path.dirname(link_file)
while True:
# get a tmp filename in the same dir as link_file
tmp = tempfile.NamedTemporaryFile(delete=True, dir=link_dir)
tmp.close()
# tmp is now deleted
# os.symlink aborts when a file with the same name already exists
# someone could have created a new file with the tmp name right in this moment
# so we need to loop and try again in this case
try:
os.symlink(target,tmp.name)
break
except FileExistsError:
pass
os.replace(tmp.name, link_file)
# ==============================================================================
# Initialization
@ -179,6 +202,64 @@ if (late_load_srm != None) and (late_load_srm != ""):
# so we have to do this manually. Note: only affects multi-user.target, nothing else
subprocess.run(["/usr/bin/systemctl", "--no-block", "start", "multi-user.target"])
# ==============================================================================
# autoterminal: programs that take over a virtual terminal for user interaction
# ==============================================================================
# expect a dict with terminal-name: command, like config['autoterminal']['tty2'] = "/usr/bin/setkmap"
if ('autoterminal' in config) and (config['autoterminal'] is not None) and \
(config['autoterminal'] is not False) and isinstance(config['autoterminal'], dict):
print("====> Configuring autoterminal ...")
with open('/usr/share/sysrescue/template/autoterminal.service', 'r') as template_file:
conf_template = template_file.read()
with open('/usr/share/sysrescue/template/serial-autoterminal.service', 'r') as template_file:
serial_conf_template = template_file.read()
start_services = []
for terminal, command in sorted(config['autoterminal'].items()):
if m := re.match(r"^serial:([a-zA-Z0-9_-]+)$", terminal):
serial=True
terminal = m.group(1)
else:
serial=False
if not re.match(r"^[a-zA-Z0-9_-]+$", terminal):
print (f"Ignoring invalid terminal name '{terminal}'")
errcnt+=1
continue
# do not check if terminal or command exists: an autorun could create them later on
if serial:
print (f"setting serial terminal '{terminal}' to '{command}'")
else:
print (f"setting terminal '{terminal}' to '{command}'")
with open(f"/etc/systemd/system/autoterminal-{terminal}.service", "w") as terminal_conf:
# write service config, based on the template config we loaded above
# don't use getty@{terminal}.service name to not use autovt@{terminal}.service on-demand logic
if serial:
conf_data=serial_conf_template.replace("%TTY%",terminal)
else:
conf_data=conf_template.replace("%TTY%",terminal)
conf_data=conf_data.replace("%EXEC%",command)
terminal_conf.write(conf_data)
# enable service: always start it, do not wait for the user to switch to the terminal
# means other programs (like X.org) can't allocate it away, also allows for longer running init sequences
symlink_overwrite(f"/etc/systemd/system/autoterminal-{terminal}.service",
f"/etc/systemd/system/getty.target.wants/autoterminal-{terminal}.service")
# mask the regular getty for this terminal
if serial:
symlink_overwrite("/dev/null",f"/etc/systemd/system/serial-getty@{terminal}.service")
else:
symlink_overwrite("/dev/null",f"/etc/systemd/system/getty@{terminal}.service")
symlink_overwrite("/dev/null",f"/etc/systemd/system/autovt@{terminal}.service")
start_services.append(f"autoterminal-{terminal}.service")
# reload systemd to allow the new config to take effect
subprocess.run(["/usr/bin/systemctl", "daemon-reload"])
# explicitly start new services (after daemon-reload): systemd can't update dependencies while starting
for s in start_services:
subprocess.run(["/usr/bin/systemctl", "--no-block", "start", s])
# ==============================================================================
# End of the script
# ==============================================================================

View file

@ -0,0 +1,60 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of SystemRescue, based on getty@.service from systemd
#
# systemd 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.
[Unit]
Description=SystemRescue autoterminal %TTY%
Documentation=https://www.system-rescue.org/manual/autoterminal/
After=systemd-user-sessions.service plymouth-quit-wait.service getty-pre.target \
getty@%TTY%.service autovt@%TTY%.service
# If additional gettys are spawned during boot then we should make
# sure that this is synchronized before getty.target, even though
# getty.target didn't actually pull it in.
Before=getty.target
IgnoreOnIsolate=yes
# IgnoreOnIsolate causes issues with sulogin, if someone isolates
# rescue.target or starts rescue.service from multi-user.target or
# graphical.target.
Conflicts=rescue.service getty@%TTY%.service autovt@%TTY%.service
Before=rescue.service
[Service]
# the VT is cleared by TTYVTDisallocate
ExecStart=-%EXEC%
# do not wait 5 seconds as for Type=idle before starting the service
Type=simple
Restart=always
RestartSec=1
UtmpIdentifier=%TTY%
StandardInput=tty
StandardOutput=tty
TTYPath=/dev/%TTY%
TTYReset=yes
TTYVHangup=yes
TTYVTDisallocate=yes
IgnoreSIGPIPE=no
SendSIGHUP=yes
# make this a systemd-logind session without needing a getty
User=root
PAMName=login
# generate all utmp/wtmp entries and don't expect the program to do it
UtmpMode=user
# Unset locale for the console getty since the console has problems
# displaying some internationalized messages.
UnsetEnvironment=LANG LANGUAGE LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY \
LC_MESSAGES LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT LC_IDENTIFICATION
[Install]
WantedBy=getty.target

View file

@ -0,0 +1,54 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of SystemRescue, based on serial-getty@.service from systemd
#
# systemd 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.
[Unit]
Description=SystemRescue serial autoterminal %TTY%
Documentation=https://www.system-rescue.org/manual/autoterminal/
BindsTo=dev-%TTY%.device
After=dev-%TTY%.device systemd-user-sessions.service plymouth-quit-wait.service \
getty-pre.target serial-getty@%TTY%.service
# If additional gettys are spawned during boot then we should make
# sure that this is synchronized before getty.target, even though
# getty.target didn't actually pull it in.
Before=getty.target
IgnoreOnIsolate=yes
# IgnoreOnIsolate causes issues with sulogin, if someone isolates
# rescue.target or starts rescue.service from multi-user.target or
# graphical.target.
Conflicts=rescue.service serial-getty@%TTY%.service
Before=rescue.service
[Service]
ExecStart=-%EXEC%
# do not wait 5 seconds as for Type=idle before starting the service
Type=simple
Restart=always
RestartSec=1
UtmpIdentifier=%TTY%
StandardInput=tty
StandardOutput=tty
TTYPath=/dev/%TTY%
TTYReset=yes
TTYVHangup=yes
IgnoreSIGPIPE=no
SendSIGHUP=yes
# make this a systemd-logind session without needing a getty
User=root
PAMName=login
# generate all utmp/wtmp entries and don't expect the program to do it
UtmpMode=user
[Install]
WantedBy=getty.target