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()