From cb697343a4540fdf3ac2351172910622fa54d2ed Mon Sep 17 00:00:00 2001 From: =?utf8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= Date: Sat, 1 Aug 2020 14:40:52 +0200 Subject: [PATCH] add initial script version --- tailerd.py | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100755 tailerd.py diff --git a/tailerd.py b/tailerd.py new file mode 100755 index 0000000..80512a3 --- /dev/null +++ b/tailerd.py @@ -0,0 +1,94 @@ +#! /usr/bin/python3 +# +# tailerd - run/tail commands as HTTP +# +# Copyright (c) 2020 Frederic Peters +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . +# +# ~~~~ +# +# This script starts a webserver and will run commands specified in its +# configuration file. It is tailored to run long-running commands such as +# tail -f and journald -f. +# +# Requirements: aiohttp +# +# Configuration: ~/.config/tailerd.ini, example: +# +# [config] +# command1 = /bin/journald -u whatever -f +# command2 = /usr/bin/tail -f /var/log/whatever.log +# +# Commands that starts with a / will be run with exec() and arguments will be +# split on spaces (no quoting). Commands that do not start with a / will be +# passed to /bin/sh -c to be interpreted by the shell. +# +# An alternate location for the configuration file can be specified using the +# -c/--config command line option. +# +# It runs on port 8080 and this can be changed using the -p/--port command line +# option. + +import argparse +import asyncio +import configparser +import os + +from aiohttp import web + + +class Tailerd: + def __init__(self, config_filename): + self.config_filename = config_filename + + async def handle(self, request): + config = configparser.ConfigParser() + config.read(os.path.join(os.path.expanduser(self.config_filename))) + try: + command = config.get('config', request.match_info['path']) + except (configparser.NoSectionError, configparser.NoOptionError): + return web.Response(status=404) + if command.startswith('/'): + command = command.split() + else: + command = ['/bin/sh', '-c', command] + response = web.StreamResponse(headers={'content-type': 'text/plain; charset=utf-8'}) + response.enable_chunked_encoding() + await response.prepare(request) + process = await asyncio.create_subprocess_exec( + *command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT, + ) + try: + while True: + data = await process.stdout.readline() + await response.write(data) + if process.returncode is not None: + await response.write(b'-- End of command: %d\n' % process.returncode) + break + return response + except asyncio.CancelledError: + if process.returncode is None: + process.terminate() + raise + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('-c', '--config', dest='config', type=str, default='~/.config/tailerd.ini') + parser.add_argument('-p', '--port', dest='port', type=int, default=8080) + args = parser.parse_args() + app = web.Application() + app.add_routes([web.get('/{path}/', Tailerd(args.config).handle)]) + web.run_app(app, port=args.port) -- 2.39.2