-
Notifications
You must be signed in to change notification settings - Fork 345
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
system: op-mode: T3334: allow delayed getty restart when configuring …
…serial ports * Created op-mode command "restart serial console" * Relocated service control to vyos.utils.serial helpers, used by conf- and op-mode serial console handling * Checking for logged-in serial sessions that may be affected by getty reconfig * Warning the user when changes are committed and serial sessions are active, otherwise restart services as normal. No prompts issued during commit, all config gen/commit steps still occur except for the service restarts (everything remains consistent) * To apply committed changes, user will need to run "restart serial console" to complete the process or reboot the whole router * Added additional flags and target filtering for generic use of helpers.
- Loading branch information
Showing
5 changed files
with
218 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
<?xml version="1.0"?> | ||
<interfaceDefinition> | ||
<node name="restart"> | ||
<children> | ||
<node name="serial"> | ||
<properties> | ||
<help>Restart services on serial ports</help> | ||
</properties> | ||
<children> | ||
<node name="console"> | ||
<properties> | ||
<help>Restart serial console service for login TTYs</help> | ||
</properties> | ||
<command>sudo ${vyos_op_scripts_dir}/serial.py restart_console</command> | ||
<children> | ||
<tagNode name="device"> | ||
<properties> | ||
<help>Restart specific TTY device</help> | ||
<completionHelp> | ||
<script>${vyos_completion_dir}/list_login_ttys.py</script> | ||
</completionHelp> | ||
</properties> | ||
<command>sudo ${vyos_op_scripts_dir}/serial.py restart_console --device-name "$5"</command> | ||
</tagNode> | ||
</children> | ||
</node> | ||
</children> | ||
</node> | ||
</children> | ||
</node> | ||
</interfaceDefinition> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
# Copyright 2024 VyOS maintainers and contributors <[email protected]> | ||
# | ||
# This library 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 library 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 library. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
import os, re, json | ||
from typing import List | ||
|
||
from vyos.base import Warning | ||
from vyos.utils.io import ask_yes_no | ||
from vyos.utils.process import cmd | ||
|
||
GLOB_GETTY_UNITS = 'serial-getty@*.service' | ||
RE_GETTY_DEVICES = re.compile(r'.+@(.+).service$') | ||
|
||
SD_UNIT_PATH = '/run/systemd/system' | ||
UTMP_PATH = '/run/utmp' | ||
|
||
def get_serial_units(include_devices=[]): | ||
# Since we cannot depend on the current config for decommissioned ports, | ||
# we just grab everything that systemd knows about. | ||
tmp = cmd(f'systemctl list-units {GLOB_GETTY_UNITS} --all --output json --no-pager') | ||
getty_units = json.loads(tmp) | ||
for sdunit in getty_units: | ||
m = RE_GETTY_DEVICES.search(sdunit['unit']) | ||
if m is None: | ||
Warning(f'Serial console unit name "{sdunit["unit"]}" is malformed and cannot be checked for activity!') | ||
continue | ||
|
||
getty_device = m.group(1) | ||
if include_devices and getty_device not in include_devices: | ||
continue | ||
|
||
sdunit['device'] = getty_device | ||
|
||
return getty_units | ||
|
||
def get_authenticated_ports(units): | ||
connected = [] | ||
ports = [ x['device'] for x in units if 'device' in x ] | ||
# | ||
# utmpdump just gives us an easily parseable dump of currently logged-in sessions, for eg: | ||
# $ utmpdump /run/utmp | ||
# Utmp dump of /run/utmp | ||
# [2] [00000] [~~ ] [reboot ] [~ ] [6.6.31-amd64-vyos ] [0.0.0.0 ] [2024-06-18T13:56:53,958484+00:00] | ||
# [1] [00051] [~~ ] [runlevel] [~ ] [6.6.31-amd64-vyos ] [0.0.0.0 ] [2024-06-18T13:57:01,790808+00:00] | ||
# [6] [03178] [tty1] [LOGIN ] [tty1 ] [ ] [0.0.0.0 ] [2024-06-18T13:57:31,015392+00:00] | ||
# [7] [37151] [ts/0] [vyos ] [pts/0 ] [10.9.8.7 ] [10.9.8.7 ] [2024-07-04T13:42:08,760892+00:00] | ||
# [8] [24812] [ts/1] [ ] [pts/1 ] [10.9.8.7 ] [10.9.8.7 ] [2024-06-20T18:10:07,309365+00:00] | ||
# | ||
# We can safely skip blank or LOGIN sessions with valid device names. | ||
# | ||
for line in cmd(f'utmpdump {UTMP_PATH}').splitlines(): | ||
row = line.split('] [') | ||
user_name = row[3].strip() | ||
user_term = row[4].strip() | ||
if user_name and user_name != 'LOGIN' and user_term in ports: | ||
connected.append(user_term) | ||
|
||
return connected | ||
|
||
def restart_login_consoles(prompt_user=False, quiet=True, devices: List[str]=[]): | ||
# restart_login_consoles() is called from both conf- and op-mode scripts, including | ||
# the warning messages and user prompts common to both. | ||
# | ||
# The default case, called with no arguments, is a simple serial-getty restart & | ||
# cleanup wrapper with no output or prompts that can be used from anywhere. | ||
# | ||
# quiet and prompt_user args have been split from an original "no_prompt", in | ||
# order to support the completely silent default use case. "no_prompt" would | ||
# only suppress the user interactive prompt. | ||
# | ||
# quiet intentionally does not suppress a vyos.base.Warning() for malformed | ||
# device names in _get_serial_units(). | ||
# | ||
cmd('systemctl daemon-reload') | ||
|
||
units = get_serial_units(devices) | ||
connected = get_authenticated_ports(units) | ||
|
||
if connected: | ||
if not quiet: | ||
print('There are user sessions connected via serial console that will be terminated\n' \ | ||
'when serial console settings are changed.\n') # extra newline is deliberate. | ||
if not prompt_user: | ||
# This flag is used by conf_mode/system_console.py to reset things, if there's | ||
# a problem, the user should issue a manual restart for serial-getty. | ||
print('Please ensure all settings are committed and saved before issuing a\n' \ | ||
'"restart serial console" command to apply new configuration.') | ||
if not prompt_user: | ||
return False | ||
if not ask_yes_no('Any uncommitted changes from these sessions will be lost and in-progress actions\n' \ | ||
'may be left in an inconsistent state. Continue?'): | ||
return False | ||
|
||
for unit in units: | ||
if 'device' not in unit: | ||
continue # malformed or filtered. | ||
unit_name = unit['unit'] | ||
unit_device = unit['device'] | ||
if os.path.exists(os.path.join(SD_UNIT_PATH, unit_name)): | ||
cmd(f'systemctl restart {unit_name}') | ||
else: | ||
# Deleted stubs don't need to be restarted, just shut them down. | ||
cmd(f'systemctl stop {unit_name}') | ||
|
||
return True |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
#!/usr/bin/env python3 | ||
# | ||
# Copyright (C) 2024 VyOS maintainers and contributors | ||
# | ||
# This program is free software; you can redistribute it and/or modify | ||
# it under the terms of the GNU General Public License version 2 or later as | ||
# published by the Free Software Foundation. | ||
# | ||
# 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 <http://www.gnu.org/licenses/>. | ||
|
||
from vyos.utils.serial import get_serial_units | ||
|
||
if __name__ == '__main__': | ||
# Autocomplete uses runtime state rather than the config tree, as a manual | ||
# restart/cleanup may be needed for deleted devices. | ||
tty_completions = [ '<text>' ] + [ x['device'] for x in get_serial_units() if 'device' in x ] | ||
print(' '.join(tty_completions)) | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
#!/usr/bin/env python3 | ||
# | ||
# Copyright (C) 2024 VyOS maintainers and contributors | ||
# | ||
# This program is free software; you can redistribute it and/or modify | ||
# it under the terms of the GNU General Public License version 2 or later as | ||
# published by the Free Software Foundation. | ||
# | ||
# 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 <http://www.gnu.org/licenses/>. | ||
|
||
import sys, typing | ||
|
||
import vyos.opmode | ||
from vyos.utils.serial import restart_login_consoles as _restart_login_consoles | ||
|
||
def restart_console(device_name: typing.Optional[str]): | ||
# Service control moved to vyos.utils.serial to unify checks and prompts. | ||
# If users are connected, we want to show an informational message and a prompt | ||
# to continue, verifying that the user acknowledges possible interruptions. | ||
if device_name: | ||
_restart_login_consoles(prompt_user=True, quiet=False, devices=[device_name]) | ||
else: | ||
_restart_login_consoles(prompt_user=True, quiet=False) | ||
|
||
if __name__ == '__main__': | ||
try: | ||
res = vyos.opmode.run(sys.modules[__name__]) | ||
if res: | ||
print(res) | ||
except (ValueError, vyos.opmode.Error) as e: | ||
print(e) | ||
sys.exit(1) |