Skip to content

Instantly share code, notes, and snippets.

@seppzer0
Last active November 20, 2023 08:40
Show Gist options
  • Save seppzer0/96bee25332ba93fcaa847f0f90114f4c to your computer and use it in GitHub Desktop.
Save seppzer0/96bee25332ba93fcaa847f0f90114f4c to your computer and use it in GitHub Desktop.
tgbotdocker.py: A compact solution for deploying a Telegram bot via Docker.
import os
import sys
import time
import signal
import subprocess
class TgBotHandler:
"""Handle a Telegram bot in a Docker container easily."""
def __init__(
self,
name_image: str,
name_container: str,
path_dockerfile: os.PathLike
) -> None:
self._name_image = name_image
self._name_container = name_container
self._path_dockerfile = path_dockerfile
@staticmethod
def _custom_cmd(cmd: str) -> None:
"""A simple 'subprocess' wrapper."""
print(f"[CMD] {cmd}")
rc = subprocess.run(cmd, shell=True).returncode
if rc != 0:
print(f"[ ! ] Could not launch: {cmd}")
sys.exit(1)
def recipe_generate(self) -> None:
"""Generate a Dockerfile to build a primitive image."""
contents = """"""\
"""FROM python:3.11-alpine3.18\n""" \
"""# prepare environment\n""" \
"""WORKDIR /app\n""" \
"""COPY . /app\n""" \
"""# get project dependencies\n""" \
"""RUN python3 -m pip install -r requirements.txt\n""" \
"""# setup CMD\n""" \
"""CMD [ "python3", "app.py" ]"""
# delete potential old recipe and generate a new one
if self._path_dockerfile in os.listdir():
os.remove(self._path_dockerfile)
with open(self._path_dockerfile, "w") as f:
f.write(contents)
def deploy(self) -> None:
"""Prepare and launch the bot."""
self._custom_cmd(f"docker build --no-cache -f {self._path_dockerfile} . -t {self._name_image}")
os.remove(self._path_dockerfile)
self._custom_cmd(f"docker run --rm -d --name {self._name_container} {self._name_image}")
print("\n[ * ] Bot is running!")
print("[ * ] Logs are below..")
# start monitoring logs
self._custom_cmd(f"docker logs --follow {self._name_container}")
def handler(self, signum: int, frame) -> None:
"""Custom SIGINT handler."""
res = input("\n\n[ ? ] Stop bot hosting? [y/n]: ").lower()
if res == "y":
print("\n[ * ] Cleaning the environment..")
self._custom_cmd(f"docker stop {self._name_container}")
# need a short time window to actually remove the container
time.sleep(1)
self._custom_cmd(f"docker rmi {self._name_image}")
print("[ * ] Done!\n")
sys.exit(1)
elif res == "n":
print("\n[ * ] Keeping the bot running..")
# keep monitoring logs
self._custom_cmd(f"docker logs --follow {self._name_container}")
else:
# assume that the bot should be kept running
print("\n[ * ] Invalid option selected, keeping the bot up..")
self._custom_cmd(f"docker logs --follow {self._name_container}")
# deploy
tgbot = TgBotHandler(
name_image="image-name",
name_container="image-name-runner",
path_dockerfile="Dockerfile"
)
tgbot.recipe_generate()
signal.signal(signal.SIGINT, tgbot.handler)
tgbot.deploy()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment