#!/usr/bin/env python3 import argparse import hashlib import logging import os import select import socket import subprocess import sys import time from nicelogger import enable_pretty_logging def run_command(command): logging.info('running command %r', command) try: subprocess.check_call(command, shell=True) except: logging.exception('failed to run command %r', command) def decode_msg(msg, secret): act, t, sig = msg.split('|') hashing = act + '|' + t + secret mysig = hashlib.sha1(hashing.encode('utf-8')).hexdigest() if mysig != sig: raise ValueError('signature mismatch') return act, int(t) def main(args, secret): af, socktype, proto, canonname, sockaddr = socket.getaddrinfo( args.host, args.port, 0, socket.SOCK_DGRAM, 0, 0)[0] sock = socket.socket(af, socktype, proto) sock.bind((args.host, args.port)) last_run = 0 while True: r, w, e = select.select([sock], [], [], args.timeout) if r: msg, remote = sock.recvfrom(4096) try: msg = msg.decode('utf-8') act, t = decode_msg(msg, secret) now = time.time() if not (act == 'update' and abs(t - now) < args.threshold): logging.warn('skipping unknown or expired msg %r from %r...', msg, remote) continue if abs(now - last_run) < args.repeat_window: logging.warn('refuse to run too frequently. last run: %r. msg %r from %r...', time.ctime(last_run), msg, remote) continue last_run = now run_command(args.command) except: logging.exception('error occurred, skipping msg %r from %r...', msg, remote) else: run_command(args.command) if __name__ == '__main__': enable_pretty_logging('INFO') parser = argparse.ArgumentParser( description='run command on archrepo2 update notification', add_help=False, ) parser.add_argument('-h', '--host', default='0.0.0.0', help='host to bind to. default: IPv4 wild address') parser.add_argument('-p', '--port', type=int, required=True, help='port to wait on') parser.add_argument('-t', '--timeout', type=float, help='timeout for waiting. will run command') parser.add_argument('-r', '--threshold', type=int, default=60, help='time threshold for message timestamp. default: 60') parser.add_argument('-w', '--repeat-window', metavar='SECS', type=int, default=60, help="don't repeat within this amount of seconds. default: 60") parser.add_argument('--help', action='help', help='show this help message and exit') parser.add_argument('command', help='command to run') args = parser.parse_args() secret = os.environ.get('REPO_SECRET', '') if not secret: logging.fatal('REPO_SECRET environment variable not set') sys.exit(1) logging.info('started') try: main(args, secret) except KeyboardInterrupt: logging.info('stopped')