From d5db3b696c29bcda58243f5f745e5311248f931f Mon Sep 17 00:00:00 2001 From: Brian Martin Date: Sun, 9 Jun 2019 20:09:35 -0400 Subject: [PATCH 1/3] dockerfile and env var Add Dockerfile, requirement.txt, and new BUMPER_CERTS env var. --- Dockerfile | 19 +++++++++++ bumper/__init__.py | 82 ++++++++++++++++++++++++++++------------------ requirements.txt | 16 +++++++++ 3 files changed, 86 insertions(+), 31 deletions(-) create mode 100644 Dockerfile create mode 100644 requirements.txt diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..85e9ea5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,19 @@ +FROM python:3.7-alpine as base + +FROM base as builder + +RUN mkdir /install +WORKDIR /install + +COPY requirements.txt /requirements.txt + +RUN pip install --install-option="--prefix=/install" -r /requirements.txt + +FROM base + +COPY --from=builder /install /usr/local +COPY . /bumper + +WORKDIR /bumper + +ENTRYPOINT ["python3", "-m", "bumper"] \ No newline at end of file diff --git a/bumper/__init__.py b/bumper/__init__.py index 104a120..f5112c2 100644 --- a/bumper/__init__.py +++ b/bumper/__init__.py @@ -15,7 +15,6 @@ import socket import sys - def strtobool(strbool): if str(strbool).lower() in ["true", "1", "t", "y", "on", "yes"]: return True @@ -24,23 +23,29 @@ def strtobool(strbool): # os.environ['PYTHONASYNCIODEBUG'] = '1' # Uncomment to enable ASYNCIODEBUG - bumper_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir)) -# Set defaults from environment variables first -# Certs -ca_cert = os.environ.get("BUMPER_CA") or os.path.join(bumper_dir, "certs", "ca.crt") -server_cert = os.environ.get("BUMPER_CERT") or os.path.join( - bumper_dir, "certs", "bumper.crt" -) -server_key = os.environ.get("BUMPER_KEY") or os.path.join( - bumper_dir, "certs", "bumper.key" -) +# Set defaults from environment variables first +print(bumper_dir) # Folders 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 +print(logs_dir) 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 +print(data_dir) +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 +print(certs_dir) + + +# Certs +ca_cert = os.environ.get("BUMPER_CA") or os.path.join(certs_dir, "ca.crt") +print(ca_cert) +server_cert = os.environ.get("BUMPER_CERT") or os.path.join(certs_dir, "bumper.crt") +print(server_cert) +server_key = os.environ.get("BUMPER_KEY") or os.path.join(certs_dir, "bumper.key") +print(server_key) # Listeners bumper_listen = os.environ.get("BUMPER_LISTEN") or socket.gethostbyname( @@ -115,16 +120,17 @@ xmppserverlog.addHandler(xmpp_rotate) logging.getLogger("asyncio").setLevel(logging.CRITICAL + 1) # Ignore this logger mqtt_listen_address = bumper_listen -mqtt_listen_port = 8883 +mqtt_listen_port = 8883 conf1_listen_address = bumper_listen conf1_listen_port = 443 conf2_listen_address = bumper_listen conf2_listen_port = 8007 -xmpp_listen_address = bumper_listen +xmpp_listen_address = bumper_listen xmpp_listen_port = 5223 + async def start(): - + try: loop = asyncio.get_event_loop() except: @@ -142,10 +148,10 @@ async def start(): 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 + return if not ( os.path.exists(ca_cert) @@ -154,7 +160,7 @@ async def start(): ): logging.log(logging.FATAL, "Certificate(s) don't exist at paths specified") return - + bumperlog.info("Starting Bumper") global mqtt_server mqtt_server = MQTTServer((mqtt_listen_address, mqtt_listen_port)) @@ -166,7 +172,9 @@ async def start(): ) global conf_server_2 conf_server_2 = ConfServer( - (conf2_listen_address, conf2_listen_port), usessl=False, helperbot=mqtt_helperbot + (conf2_listen_address, conf2_listen_port), + usessl=False, + helperbot=mqtt_helperbot, ) global xmpp_server xmpp_server = XMPPServer((xmpp_listen_address, xmpp_listen_port)) @@ -438,6 +446,7 @@ class VacBotDevice(object): "xmpp_connection": self.xmpp_connection, } + class GlobalVacBotDevice(VacBotDevice): # EcoVacs Home UILogicId = "" ota = True @@ -944,11 +953,13 @@ API_ERRORS = { ERR_WRONG_PWD_FROMATE: "1009", } + 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) @@ -958,9 +969,7 @@ def create_certs(): os.chdir("certs") if str(platform.system()).lower() == "windows": # run for win - subprocess.run( - [os.path.join("..", "create_certs", "create_certs_windows.exe")] - ) + 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")]) @@ -970,15 +979,16 @@ def create_certs(): subprocess.run([os.path.join("..", "create_certs", "create_certs_rpi")]) else: # run for linux - subprocess.run( - [os.path.join("..", "create_certs", "create_certs_linux")] - ) - + subprocess.run([os.path.join("..", "create_certs", "create_certs_linux")]) + else: - logging.log(logging.FATAL, "Can't determine platform. Create certs manually and try again.") + logging.log( + logging.FATAL, + "Can't determine platform. Create certs manually and try again.", + ) return - print("Certificates created") + print("Certificates created") os.chdir(odir) print(os.path.realpath(os.curdir)) if "__main__.py" in sys.argv[0]: @@ -989,22 +999,29 @@ def create_certs(): else: os.execv(sys.executable, ["python"] + sys.argv) # Start again -def firstrun_input(): + +def firstrun_input(): return input( "No certificates found, would you like to create them automatically? (y/n): " ).lower() + def first_run(): yes = {"yes", "y", "ye", ""} print("") if firstrun_input() in yes: - create_certs() + create_certs() else: - logging.log(logging.FATAL, "Can't continue without certificates, please create some then try again.") + logging.log( + logging.FATAL, + "Can't continue without certificates, please create some then try again.", + ) + def main(argv=None): import argparse + global bumper_debug global bumper_listen global bumper_announce_ip @@ -1024,7 +1041,10 @@ def main(argv=None): "--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" + "--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) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..1727715 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,16 @@ +-i https://pypi.python.org/simple +aiohttp==4.0.0a0 +async-timeout==3.0.1 +attrs==19.1.0 +chardet==3.0.4 +docopt==0.6.2 +hbmqtt==0.9.5 +idna==2.8 +multidict==4.5.2 +passlib==1.7.1 +pyyaml==5.1.1 +six==1.12.0 +tinydb==3.13.0 +transitions==0.6.9 +websockets==7.0 +yarl==1.3.0 -- 2.39.5 From 6b2ed31c9e8b2e97e8911a81f468bb3ad4d60c81 Mon Sep 17 00:00:00 2001 From: Brian Martin Date: Mon, 10 Jun 2019 22:10:18 -0400 Subject: [PATCH 2/3] remove first run prompt - fix tests remove first run prompt, default to generate certs fix tests --- bumper/__init__.py | 18 ++---------------- tests/test_init.py | 2 +- tests/test_z_problem.py | 42 +++++++++++++++++------------------------ 3 files changed, 20 insertions(+), 42 deletions(-) diff --git a/bumper/__init__.py b/bumper/__init__.py index f5112c2..fda6461 100644 --- a/bumper/__init__.py +++ b/bumper/__init__.py @@ -982,6 +982,7 @@ def create_certs(): 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.", @@ -1000,23 +1001,8 @@ def create_certs(): os.execv(sys.executable, ["python"] + sys.argv) # Start again -def firstrun_input(): - return input( - "No certificates found, would you like to create them automatically? (y/n): " - ).lower() - - def first_run(): - yes = {"yes", "y", "ye", ""} - print("") - if firstrun_input() in yes: - create_certs() - - else: - logging.log( - logging.FATAL, - "Can't continue without certificates, please create some then try again.", - ) + create_certs() def main(argv=None): diff --git a/tests/test_init.py b/tests/test_init.py index 88faec4..0e3ca2c 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -41,7 +41,7 @@ async def test_start_stop(): await asyncio.sleep(0.1) l.check_present(("bumper", "INFO", "Starting Bumper")) l.clear() - assert b.shutting_down == False + asyncio.create_task(b.shutdown()) await asyncio.sleep(0.1) l.check_present( diff --git a/tests/test_z_problem.py b/tests/test_z_problem.py index bf93b00..f3f00f2 100644 --- a/tests/test_z_problem.py +++ b/tests/test_z_problem.py @@ -13,29 +13,10 @@ from testfixtures import LogCapture import sys -@patch("bumper.firstrun_input") -@patch("bumper.create_certs") -def test_firstrun(mock_input, mock_create): - with LogCapture() as l: - - bumper.firstrun_input.return_value = "n" - bumper.first_run() - l.check_present( - ( - "root", - "CRITICAL", - "Can't continue without certificates, please create some then try again.", - ) - ) - - bumper.firstrun_input.return_value = "y" - bumper.first_run() - assert mock_create.called == True - - def mock_subrun(*args): return args + @patch("bumper.start") def test_argparse(mock_start): bumper.ca_cert = "tests/test_certs/ca.crt" @@ -70,23 +51,35 @@ def test_createcert(mock_run, mock_platform, mock_machine, mock_exec): platform.system.return_value = "darwin" bumper.create_certs() assert mock_run.called == True - assert os.path.join("..", "create_certs", "create_certs_osx") in mock_exec.call_args.args[0] + assert ( + os.path.join("..", "create_certs", "create_certs_osx") + in mock_exec.call_args.args[0] + ) platform.system.return_value = "windows" bumper.create_certs() assert mock_run.called == True - assert os.path.join("..", "create_certs", "create_certs_windows.exe") in mock_exec.call_args.args[0] + assert ( + os.path.join("..", "create_certs", "create_certs_windows.exe") + in mock_exec.call_args.args[0] + ) platform.system.return_value = "linux" bumper.create_certs() assert mock_run.called == True - assert os.path.join("..", "create_certs","create_certs_linux") in mock_exec.call_args.args[0] + assert ( + os.path.join("..", "create_certs", "create_certs_linux") + in mock_exec.call_args.args[0] + ) platform.system.return_value = "linux" platform.machine.return_value = "arm" bumper.create_certs() assert mock_run.called == True - assert os.path.join("..", "create_certs", "create_certs_rpi") in mock_exec.call_args.args[0] + assert ( + os.path.join("..", "create_certs", "create_certs_rpi") + in mock_exec.call_args.args[0] + ) with LogCapture() as l: platform.system.return_value = "nixbad" @@ -101,7 +94,6 @@ def test_createcert(mock_run, mock_platform, mock_machine, mock_exec): ) - @patch("bumper.first_run") def test_main(mock_firstrun): bumper.ca_cert = "sf" -- 2.39.5 From 52d192efed6eaba7fa62890c8ce207cc3a517191 Mon Sep 17 00:00:00 2001 From: Brian Martin Date: Sat, 15 Jun 2019 01:01:04 -0400 Subject: [PATCH 3/3] Update README.md --- README.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/README.md b/README.md index 4e7d23c..d88b49d 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,44 @@ Bumper has a number of available command-line arguments that can be viewed by ad Bumper looks for a number of Environment Variables at initialization allowing for customizing a number of settings. For more information see the [Environment Variables](docs/Env_Var.md) doc. + +## Docker + +### Docker build + +To build the docker image you can run the following: +`docker build -t bumper .` + +This requires Docker 17.09 or newer, but has also been tested with podman. + +### Docker usage + +To run the image in docker some environment settings and port mappings are required: + +**Ports Required: (-p)** +- 443 - `-p 443:443` +- 8007 - `-p 8007:8007` +- 8883 - `-p 8883:8883` +- 5223 - `-p 5223:5223` + +**Environment Settings: (-e)** + +BUMPER_ANNOUNCE_IP should be used so the actual host IP is reported to bots that checkin. +- BUMPER_ANNOUNCE_IP - `-e "BUMPER_ANNOUNCE_IP=X.X.X.X"` + +**Volume Settings: (-v)** + +Optionally you can map existing directories for logs, data, and certs. + +- data/logs/certs +- Data - `-v /home/user/bumper/data:/bumper/data` + +**Full Example:** + +```` +docker run -it -e "BUMPER_ANNOUNCE_IP=X.X.X.X" -p 443:443 -p 8007:8007 -p 8883:8883 -p 5223:5223 -v /home/user/bumper/data:/bumper/data --name bumper bumper +```` + ## Thanks A big thanks to the original project creator @torbjornaxelsson, without his work this project would have taken much longer to build. -- 2.39.5