bumper/bumper/__init__.py
2021-04-19 21:04:14 +02:00

411 lines
12 KiB
Python

#!/usr/bin/env python3
from bumper.confserver import ConfServer
from bumper.mqttserver import MQTTServer, MQTTHelperBot
from bumper.xmppserver import XMPPServer
from bumper.models import *
from bumper.db import *
import asyncio
import os
import logging
from logging.handlers import RotatingFileHandler
import socket
import sys
import importlib
import pkgutil
from pkgutil import extend_path
def strtobool(strbool):
if str(strbool).lower() in ["true", "1", "t", "y", "on", "yes"]:
return True
else:
return False
# os.environ['PYTHONASYNCIODEBUG'] = '1' # Uncomment to enable ASYNCIODEBUG
bumper_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
log_to_stdout = os.environ.get("LOG_TO_STDOUT")
# Set defaults from environment variables first
# Folders
if not log_to_stdout:
logs_dir = os.environ.get("BUMPER_LOGS") or os.path.join(bumper_dir, "logs")
os.makedirs(logs_dir, exist_ok=True) # Ensure logs directory exists or create
data_dir = os.environ.get("BUMPER_DATA") or os.path.join(bumper_dir, "data")
os.makedirs(data_dir, exist_ok=True) # Ensure data directory exists or create
certs_dir = os.environ.get("BUMPER_CERTS") or os.path.join(bumper_dir, "certs")
os.makedirs(certs_dir, exist_ok=True) # Ensure data directory exists or create
# Certs
ca_cert = os.environ.get("BUMPER_CA") or os.path.join(certs_dir, "ca.crt")
server_cert = os.environ.get("BUMPER_CERT") or os.path.join(certs_dir, "bumper.crt")
server_key = os.environ.get("BUMPER_KEY") or os.path.join(certs_dir, "bumper.key")
# Listeners
bumper_listen = os.environ.get("BUMPER_LISTEN") or socket.gethostbyname(
socket.gethostname()
)
bumper_announce_ip = os.environ.get("BUMPER_ANNOUNCE_IP") or bumper_listen
# Other
bumper_debug = strtobool(os.environ.get("BUMPER_DEBUG")) or False
use_auth = False
token_validity_seconds = 3600 # 1 hour
oauth_validity_days = 15
db = None
mqtt_server = None
mqtt_helperbot = None
conf_server = None
conf_server_2 = None
xmpp_server = None
# Plugins
sys.path.append(os.path.join(bumper_dir, "bumper", "plugins"))
sys.path.append(os.path.join(data_dir, "plugins"))
discovered_plugins = {
name: importlib.import_module(name)
for finder, name, ispkg in pkgutil.iter_modules()
if name.startswith('bumper_')
}
shutting_down = False
# Set format for all logs
logformat = logging.Formatter(
"[%(asctime)s] :: %(levelname)s :: %(name)s :: %(module)s :: %(funcName)s :: %(lineno)d :: %(message)s"
)
bumperlog = logging.getLogger("bumper")
if not log_to_stdout:
bumper_rotate = RotatingFileHandler("logs/bumper.log", maxBytes=5000000, backupCount=5)
bumper_rotate.setFormatter(logformat)
bumperlog.addHandler(bumper_rotate)
else:
bumperlog.addHandler(logging.StreamHandler(sys.stdout))
# Override the logging level
# bumperlog.setLevel(logging.INFO)
confserverlog = logging.getLogger("confserver")
if not log_to_stdout:
conf_rotate = RotatingFileHandler(
"logs/confserver.log", maxBytes=5000000, backupCount=5
)
conf_rotate.setFormatter(logformat)
confserverlog.addHandler(conf_rotate)
else:
confserverlog.addHandler(logging.StreamHandler(sys.stdout))
# Override the logging level
# confserverlog.setLevel(logging.INFO)
mqttserverlog = logging.getLogger("mqttserver")
if not log_to_stdout:
mqtt_rotate = RotatingFileHandler(
"logs/mqttserver.log", maxBytes=5000000, backupCount=5
)
mqtt_rotate.setFormatter(logformat)
mqttserverlog.addHandler(mqtt_rotate)
else:
mqttserverlog.addHandler(logging.StreamHandler(sys.stdout))
# Override the logging level
# mqttserverlog.setLevel(logging.INFO)
### Additional MQTT Logs
translog = logging.getLogger("transitions")
if not log_to_stdout:
translog.addHandler(mqtt_rotate)
else:
translog.addHandler(logging.StreamHandler(sys.stdout))
translog.setLevel(logging.CRITICAL + 1) # Ignore this logger
logging.getLogger("passlib").setLevel(logging.CRITICAL + 1) # Ignore this logger
brokerlog = logging.getLogger("hbmqtt.broker")
#brokerlog.setLevel(
# logging.CRITICAL + 1
#) # Ignore this logger #There are some sublogs that could be set if needed (.plugins)
if not log_to_stdout:
brokerlog.addHandler(mqtt_rotate)
else:
brokerlog.addHandler(logging.StreamHandler(sys.stdout))
protolog = logging.getLogger("hbmqtt.mqtt.protocol")
#protolog.setLevel(
# logging.CRITICAL + 1
#) # Ignore this logger
if not log_to_stdout:
protolog.addHandler(mqtt_rotate)
else:
protolog.addHandler(logging.StreamHandler(sys.stdout))
clientlog = logging.getLogger("hbmqtt.client")
#clientlog.setLevel(logging.CRITICAL + 1) # Ignore this logger
if not log_to_stdout:
clientlog.addHandler(mqtt_rotate)
else:
clientlog.addHandler(logging.StreamHandler(sys.stdout))
helperbotlog = logging.getLogger("helperbot")
if not log_to_stdout:
helperbot_rotate = RotatingFileHandler(
"logs/helperbot.log", maxBytes=5000000, backupCount=5
)
helperbot_rotate.setFormatter(logformat)
helperbotlog.addHandler(helperbot_rotate)
else:
helperbotlog.addHandler(logging.StreamHandler(sys.stdout))
# Override the logging level
# helperbotlog.setLevel(logging.INFO)
boterrorlog = logging.getLogger("boterror")
if not log_to_stdout:
boterrorlog_rotate = RotatingFileHandler(
"logs/boterror.log", maxBytes=5000000, backupCount=5
)
boterrorlog_rotate.setFormatter(logformat)
boterrorlog.addHandler(boterrorlog_rotate)
else:
boterrorlog.addHandler(logging.StreamHandler(sys.stdout))
# Override the logging level
# boterrorlog.setLevel(logging.INFO)
xmppserverlog = logging.getLogger("xmppserver")
if not log_to_stdout:
xmpp_rotate = RotatingFileHandler(
"logs/xmppserver.log", maxBytes=5000000, backupCount=5
)
xmpp_rotate.setFormatter(logformat)
xmppserverlog.addHandler(xmpp_rotate)
else:
xmppserverlog.addHandler(logging.StreamHandler(sys.stdout))
# Override the logging level
# xmppserverlog.setLevel(logging.INFO)
logging.getLogger("asyncio").setLevel(logging.CRITICAL + 1) # Ignore this logger
mqtt_listen_port = 8883
conf1_listen_port = 443
conf2_listen_port = 8007
xmpp_listen_port = 5223
async def start():
try:
loop = asyncio.get_event_loop()
except:
loop = asyncio.new_event_loop()
if bumper_debug:
logging.basicConfig(
level=logging.DEBUG,
format="[%(asctime)s] :: %(levelname)s :: %(name)s :: %(module)s :: %(funcName)s :: %(lineno)d :: %(message)s",
)
loop.set_debug(True) # Set asyncio loop to debug
# logging.getLogger("asyncio").setLevel(logging.DEBUG) # Show debug asyncio logs (disabled in init, uncomment for debugging asyncio)
else:
logging.basicConfig(
level=logging.INFO,
format="[%(asctime)s] :: %(levelname)s :: %(name)s :: %(message)s",
)
if not bumper_listen:
logging.log(logging.FATAL, "No listen address configured")
return
if not (
os.path.exists(ca_cert)
and os.path.exists(server_cert)
and os.path.exists(server_key)
):
logging.log(logging.FATAL, "Certificate(s) don't exist at paths specified")
return
bumperlog.info("Starting Bumper")
global mqtt_server
mqtt_server = MQTTServer((bumper_listen, mqtt_listen_port))
global mqtt_helperbot
mqtt_helperbot = MQTTHelperBot((bumper_listen, mqtt_listen_port))
global conf_server
conf_server = ConfServer((bumper_listen, conf1_listen_port), usessl=True)
global conf_server_2
conf_server_2 = ConfServer((bumper_listen, conf2_listen_port), usessl=False)
global xmpp_server
xmpp_server = XMPPServer((bumper_listen, xmpp_listen_port))
# Start MQTT Server
asyncio.create_task(mqtt_server.broker_coro())
# Start MQTT Helperbot
asyncio.create_task(mqtt_helperbot.start_helper_bot())
# Start XMPP Server
asyncio.create_task(xmpp_server.start_async_server())
# Wait for helperbot to connect first
while mqtt_helperbot.Client is None:
await asyncio.sleep(0.1)
while not mqtt_helperbot.Client.session.transitions.state == "connected":
await asyncio.sleep(0.1)
# Start web servers
conf_server.confserver_app()
asyncio.create_task(conf_server.start_site(conf_server.app, address=bumper_listen, port=conf1_listen_port, usessl=True))
asyncio.create_task(conf_server.start_site(conf_server.app, address=bumper_listen, port=conf2_listen_port, usessl=False))
# Start maintenance
while not shutting_down:
asyncio.create_task(maintenance())
await asyncio.sleep(5)
async def maintenance():
revoke_expired_tokens()
revoke_expired_oauths()
async def shutdown():
try:
bumperlog.info("Shutting down")
await conf_server.stop_server()
await conf_server_2.stop_server()
if mqtt_server.broker.transitions.state == "started":
await mqtt_server.broker.shutdown()
elif mqtt_server.broker.transitions.state == "starting":
while mqtt_server.broker.transitions.state == "starting":
await asyncio.sleep(0.1)
if mqtt_server.broker.transitions.state == "started":
await mqtt_server.broker.shutdown()
await mqtt_helperbot.Client.disconnect()
if xmpp_server.server:
if xmpp_server.server._serving:
xmpp_server.server.close()
await xmpp_server.server.wait_closed()
global shutting_down
shutting_down = True
except asyncio.CancelledError:
bumperlog.info("Coroutine canceled")
except Exception as e:
bumperlog.info("Exception: {}".format(e))
finally:
bumperlog.info("Shutdown complete")
def create_certs():
import platform
import os
import subprocess
import sys
path = os.path.dirname(sys.modules[__name__].__file__)
path = os.path.join(path, "..")
sys.path.insert(0, path)
print("Creating certificates")
odir = os.path.realpath(os.curdir)
os.chdir("certs")
if str(platform.system()).lower() == "windows":
# run for win
subprocess.run([os.path.join("..", "create_certs", "create_certs_windows.exe")])
elif str(platform.system()).lower() == "darwin":
# run on mac
subprocess.run([os.path.join("..", "create_certs", "create_certs_osx")])
elif str(platform.system()).lower() == "linux":
if "arm" in platform.machine().lower() or "aarch64" in platform.machine().lower():
# run for pi
subprocess.run([os.path.join("..", "create_certs", "create_certs_rpi")])
else:
# run for linux
subprocess.run([os.path.join("..", "create_certs", "create_certs_linux")])
else:
os.chdir(odir)
logging.log(
logging.FATAL,
"Can't determine platform. Create certs manually and try again.",
)
return
print("Certificates created")
os.chdir(odir)
if "__main__.py" in sys.argv[0]:
os.execv(
sys.executable, ["python", "-m", "bumper"] + sys.argv[1:]
) # Start again
else:
os.execv(sys.executable, ["python"] + sys.argv) # Start again
def first_run():
create_certs()
def main(argv=None):
import argparse
global bumper_debug
global bumper_listen
global bumper_announce_ip
if not argv:
argv = sys.argv[1:] # Set argv to argv[1:] if not passed into main
try:
if not (
os.path.exists(ca_cert)
and os.path.exists(server_cert)
and os.path.exists(server_key)
):
first_run()
return
if not (
os.path.exists(os.path.join(data_dir, "passwd"))
):
with open(os.path.join(data_dir, "passwd"), 'w'): pass
parser = argparse.ArgumentParser()
parser.add_argument(
"--listen", type=str, default=None, help="start serving on address"
)
parser.add_argument(
"--announce",
type=str,
default=None,
help="announce address to bots on checkin",
)
parser.add_argument("--debug", action="store_true", help="enable debug logs")
args = parser.parse_args(args=argv)
if args.debug:
bumper_debug = True
if args.listen:
bumper_listen = args.listen
if args.announce:
bumper_announce_ip = args.announce
asyncio.run(start())
except KeyboardInterrupt:
bumperlog.info("Keyboard Interrupt!")
pass
except Exception as e:
bumperlog.exception(e)
pass
finally:
asyncio.run(shutdown())