Files
samba-member/shutdown_on_exit.py
2026-03-03 22:18:19 +00:00

111 lines
3.1 KiB
Python

#!/usr/bin/env python3
"""
Shutdown supervisord when a selected program (or any program) exits.
Usage:
shutdown_on_exit.py <program_name_or_asterisk>
Examples:
shutdown_on_exit.py myprogram # only when 'myprogram' exits
shutdown_on_exit.py * # when ANY program exits
"""
import os
import sys
import subprocess
# ---- Supervisor event listener protocol helpers ----
def read_header(fd):
"""Read a line like 'ver:3.0 server:supervisor serial:7 eventname:PROCESS_STATE_EXITED len:123'."""
line = fd.readline()
if not line:
return None
header = {}
for part in line.strip().split():
if ":" in part:
k, v = part.split(":", 1)
header[k] = v
return header
def read_payload(fd, length):
"""Read payload of given length into a string."""
if length is None:
return ""
length = int(length)
return fd.read(length)
def write_stdout(s):
sys.stdout.write(s)
sys.stdout.flush()
def write_stderr(s):
sys.stderr.write(s)
sys.stderr.flush()
def parse_processname_from_payload(payload):
"""
Payload is key=value lines. We want 'processname=<name>'.
Example payload:
processname:myprogram groupname:myprogram from_state:RUNNING pid:1234
Or sometimes 'processname=' is shown as 'processname:'
We'll accept both ':' and '=' separators to be safe.
"""
name = None
for token in payload.replace("\n", " ").split():
if token.startswith("processname"):
if ":" in token:
_, val = token.split(":", 1)
elif "=" in token:
_, val = token.split("=", 1)
else:
val = ""
name = val.strip()
return name
def do_shutdown():
try:
subprocess.run(
["supervisorctl", "shutdown"],
check=True,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
except Exception as e:
write_stderr(f"shutdown_on_exit: failed to call supervisorctl shutdown: {e}\n")
def main():
if len(sys.argv) != 2:
write_stderr("Usage: shutdown_on_exit.py <program_name_or_asterisk>\n")
sys.exit(2)
# '*' or a specific program name
target = sys.argv[1]
write_stdout("READY\n")
while True:
header = read_header(sys.stdin)
if header is None:
break
payload = read_payload(sys.stdin, header.get("len"))
eventname = header.get("eventname", "")
if eventname == "PROCESS_STATE_EXITED":
procname = parse_processname_from_payload(payload) or ""
should_shutdown = (target == "*") or (procname == target)
if should_shutdown:
write_stderr(
f"shutdown_on_exit: triggering shutdown due to exit of '{procname}'\n"
)
# Acknowledge handling BEFORE shutdown to avoid protocol deadlock
write_stdout("RESULT 2\nOK")
do_shutdown()
sys.exit(0)
# Acknowledge we processed the event
write_stdout("RESULT 2\nOK")
if __name__ == "__main__":
main()