#!/usr/bin/env python3
# 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")