From 46de80a7af4969fc1585f6ef89443daaa66a2aad Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Sat, 11 Jul 2026 14:47:43 -0400 Subject: [PATCH] Insure that sshd is started. Provide more helpful messages through out the process for the person receiving assistance. --- sas.py | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 76 insertions(+), 5 deletions(-) diff --git a/sas.py b/sas.py index 4b289d5..522ea9a 100755 --- a/sas.py +++ b/sas.py @@ -19,6 +19,7 @@ ircPort = 6667 ircChannel = "#stormux" remoteHost = "billysballoons.com" sasUser = "sas" +sshdService = "sshd" pingIntervalSeconds = 180 pingCount = 5 maxWormholeFailures = 3 @@ -97,6 +98,32 @@ def run_as_user(userName, command, useSpeech, check=True): return run_command(fullCommand, check=check) +def command_error_text(result): + return (result.stderr or result.stdout or "").strip() + + +def service_is_active(serviceName): + result = run_command(["systemctl", "is-active", "--quiet", serviceName]) + return result.returncode == 0 + + +def start_service(serviceName, useSpeech): + result = run_privileged(["systemctl", "start", serviceName], useSpeech, check=False) + if result.returncode == 0: + return + errorText = command_error_text(result) + if errorText: + raise RuntimeError(f"Failed to start {serviceName}: {errorText}") + raise RuntimeError(f"Failed to start {serviceName}") + + +def stop_service(serviceName, useSpeech): + result = run_privileged(["systemctl", "stop", serviceName], useSpeech, check=False) + if result.returncode == 0: + return True + return False + + def user_exists(userName): result = run_command(["getent", "passwd", userName]) return result.returncode == 0 @@ -351,13 +378,14 @@ def main(): tempDir = tempfile.mkdtemp(prefix="sas-ii-") ircSession = None sshProcess = None + sshdWasActiveAtStart = service_is_active(sshdService) + shouldStopSshdOnCleanup = False def cleanup(exitMessage=None): - nonlocal cleanupDone + nonlocal cleanupDone, sshProcess if cleanupDone: return cleanupDone = True - nonlocal sshProcess if exitMessage: say_or_print(exitMessage, useSpeech) @@ -371,6 +399,20 @@ def main(): if ircSession: ircSession.stop() + if shouldStopSshdOnCleanup: + say_or_print("Stopping sshd; it was not active when sas started.", useSpeech) + try: + if not stop_service(sshdService, useSpeech): + say_or_print( + "Cleanup warning: failed to stop sshd. Please stop it manually.", + useSpeech, + ) + except Exception: + say_or_print( + "Cleanup warning: failed to stop sshd. Please stop it manually.", + useSpeech, + ) + if shouldRemoveUser: try: run_privileged(["pkill", "-u", sasUser], useSpeech, check=False) @@ -398,10 +440,24 @@ def main(): cleanup("Interrupted. Cleaning up.") sys.exit(1) - signal.signal(signal.SIGINT, handle_signal) - signal.signal(signal.SIGTERM, handle_signal) + def ensure_sshd_active(): + nonlocal shouldStopSshdOnCleanup + if service_is_active(sshdService): + shouldStopSshdOnCleanup = not sshdWasActiveAtStart + return + say_or_print("Starting sshd for this support session.", useSpeech) + start_service(sshdService, useSpeech) + if not service_is_active(sshdService): + raise RuntimeError("sshd did not become active") + shouldStopSshdOnCleanup = not sshdWasActiveAtStart + + for signalName in ("SIGINT", "SIGTERM", "SIGHUP", "SIGQUIT"): + if hasattr(signal, signalName): + signal.signal(getattr(signal, signalName), handle_signal) try: + say_or_print("Preparing your support session.", useSpeech) + if not user_exists(sasUser): run_privileged( ["useradd", "-m", "-d", f"/home/{sasUser}", "-s", "/bin/bash", "-G", "wheel", sasUser], @@ -443,7 +499,7 @@ def main(): ircSession.start() ircSession.join_channel() - say_or_print("Waiting for assistance on IRC.", useSpeech) + say_or_print("Waiting for a Stormux assistant to respond.", useSpeech) startTime = time.monotonic() nextPingTime = startTime pingsSent = 0 @@ -468,11 +524,18 @@ def main(): cleanup("No one was available to help, please try again later.") return 1 + say_or_print( + "An assistant has responded to your support request and should connect shortly.", + useSpeech, + ) + ircSession.send_private_message( confirmedAdmin, f'password: "{password}" please send wormhole ssh invite code', ) + say_or_print("Exchanging secure connection information. This may take a moment.", useSpeech) + failures = 0 while failures < maxWormholeFailures: inviteCode = None @@ -500,6 +563,11 @@ def main(): say_or_print("Wormhole key transfer succeeded.", useSpeech) break + say_or_print( + "That connection code did not work. Waiting for a new code from the assistant.", + useSpeech, + ) + failures += 1 errorTextFull = (result.stderr or result.stdout or "").strip() if errorTextFull and not useSpeech: @@ -523,6 +591,8 @@ def main(): cleanup("Wormhole failed too many times. Exiting.") return 1 + ensure_sshd_active() + say_or_print("Starting reverse SSH tunnel. Press Ctrl+C to stop.", useSpeech) sshCommand = [ "ssh", @@ -548,6 +618,7 @@ def main(): sshCommand = ["sudo", "-u", sasUser, "-H"] + sshCommand sshProcess = subprocess.Popen(sshCommand) sshProcess.wait() + say_or_print("The support connection ended. Cleaning up.", useSpeech) except Exception as exc: cleanup(f"Error: {exc}")