#!/usr/bin/python # SPDX-License-Identifier: MIT-0 # # Implement the systemd notify protocol without external dependencies. # Supports both readiness notification on startup and on reloading, # according to the protocol defined at: # https://www.freedesktop.org/software/systemd/man/latest/sd_notify.html # This protocol is guaranteed to be stable as per: # https://systemd.io/PORTABILITY_AND_STABILITY/ import errno import os import signal import socket import sys import time reloading = False terminating = False def notify(message): if not message: raise ValueError('notify() requires a message') socket_path = os.environ.get('NOTIFY_SOCKET') if not socket_path: return if socket_path[0] not in ('/', '@'): raise OSError(errno.EAFNOSUPPORT, 'Unsupported socket type') # Handle abstract socket. if socket_path[0] == '@': socket_path = '\0' + socket_path[1:] with socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM | socket.SOCK_CLOEXEC) as sock: sock.connect(socket_path) sock.sendall(message) def notify_ready(): notify(b'READY=1') def notify_reloading(): microsecs = time.clock_gettime_ns(time.CLOCK_MONOTONIC) // 1000 notify(f'RELOADING=1\nMONOTONIC_USEC={microsecs}'.encode()) def notify_stopping(): notify(b'STOPPING=1') def reload(signum, frame): global reloading reloading = True def terminate(signum, frame): global terminating terminating = True def main(): print('Doing initial setup') global reloading, terminating # Set up signal handlers. print('Setting up signal handlers') signal.signal(signal.SIGHUP, reload) signal.signal(signal.SIGINT, terminate) signal.signal(signal.SIGTERM, terminate) # Do any other setup work here. # Once all setup is done, signal readiness. print('Done setting up') notify_ready() print('Starting loop') while not terminating: if reloading: print('Reloading') reloading = False # Support notifying the manager when reloading configuration. # This allows accurate state tracking as well as automatically # enabling 'systemctl reload' without needing to manually # specify an ExecReload= line in the unit file. notify_reloading() # Do some reconfiguration work here. print('Done reloading') notify_ready() # Do the real work here ... print('Sleeping for five seconds') time.sleep(5) print('Terminating') notify_stopping() if __name__ == '__main__': sys.stdout.reconfigure(line_buffering=True) print('Starting app') main() print('Stopped app')