From 3926727a284a36e876cfb05cce7bfba895277c44 Mon Sep 17 00:00:00 2001 From: Kylmakalle Date: Fri, 13 Apr 2018 15:05:19 +0300 Subject: [PATCH] Docker ready --- .gitignore | 5 +++- Dockerfile | 11 ++++++++ README.md | 14 ++++++---- bot.py | 2 ++ conf.d/web.conf | 23 +++++++++++++++++ config.py | 24 +++++++++-------- docker-compose.yml | 45 ++++++++++++++++++++++++++++++++ install.sh | 10 ++++++++ obtaincert.py | 35 +++++++++++++++++++++++++ set_env.py | 64 ++++++++++++++++++++++++++++++++++++++++++++++ telegram.py | 12 ++------- vk_messages.py | 7 +++-- 12 files changed, 224 insertions(+), 28 deletions(-) create mode 100644 Dockerfile create mode 100644 conf.d/web.conf create mode 100644 docker-compose.yml create mode 100755 install.sh create mode 100644 obtaincert.py create mode 100644 set_env.py diff --git a/.gitignore b/.gitignore index 81c14a5..12fad12 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,7 @@ __pycache__/ data/migrations/* !data/migrations/__init__.py -config.py \ No newline at end of file +config.py +*.pem +env_file +openssl_config diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..7fff452 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM python:3.6 +# MAINTAINER Sergey (@Kylmakalle) + +ENV PYTHONUNBUFFERED 1 +RUN mkdir /src +WORKDIR /src +COPY requirements.txt /src/ +RUN pip install -r requirements.txt +COPY . /src + + diff --git a/README.md b/README.md index 158c916..56b1760 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,15 @@ # tgvkbot Send and recieve VK messages in telegram. +# Установка (Ubuntu 16.04) ``` -git clone ... +git clone https://github.com/Kylmakalle/tgvkbot cd tgvkbot -pip install -r requirements.txt -# Edit config.py file -python manage.py makemigrations data && python manage.py migrate data -python telegram.py +chmod +x install.sh +./install.sh + +... + +Telegram Token: 123456789:AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLL +VK APP ID: 1234567 ``` diff --git a/bot.py b/bot.py index 319cf2c..455c45b 100644 --- a/bot.py +++ b/bot.py @@ -14,6 +14,7 @@ from aiogram.dispatcher import Dispatcher from aiogram.types import ParseMode, MediaGroup, InlineKeyboardMarkup, InlineKeyboardButton, ChatActions from aiogram.utils.exceptions import * from aiogram.utils.parts import safe_split_text, split_text, MAX_MESSAGE_LENGTH +from aiogram.utils import context from aiovk import TokenSession, API from aiovk.drivers import HttpDriver from aiovk.exceptions import * @@ -100,3 +101,4 @@ async def get_content(url, docname='tgvkbot.document', chrome_headers=True, rewr bot = Bot(token=BOT_TOKEN) dp = Dispatcher(bot) +dp.loop.set_task_factory(context.task_factory) diff --git a/conf.d/web.conf b/conf.d/web.conf new file mode 100644 index 0000000..3c1b43e --- /dev/null +++ b/conf.d/web.conf @@ -0,0 +1,23 @@ +upstream tgbot { + ip_hash; + server tgbot:7777; +} + + +server { + listen 8443 ssl; + ssl_certificate /src/webhook_cert.pem; + ssl_certificate_key /src/webhook_pkey.pem; + + location / { + proxy_redirect off; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $server_name; + + proxy_pass http://tgbot/; + + } + +} \ No newline at end of file diff --git a/config.py b/config.py index 5add03c..8e14bdf 100644 --- a/config.py +++ b/config.py @@ -10,23 +10,27 @@ def get_external_host(): HOST = '' WEBHOOK_HOST = os.environ.get('WEBHOOK_HOST', HOST or get_external_host()) -WEBHOOK_PORT = os.environ.get('WEBHOOK_PORT', 443) +WEBHOOK_PORT = os.environ.get('WEBHOOK_PORT', 8443) WEBHOOK_URL_PATH = os.environ.get('WEBHOOK_URL_PATH', '/tgwebhook') -WEBHOOK_URL = f"https://{WEBHOOK_HOST}:{WEBHOOK_PORT}{WEBHOOK_URL_PATH}" +WEBHOOK_URL = "https://{WEBHOOK_HOST}:{WEBHOOK_PORT}{WEBHOOK_URL_PATH}".format(WEBHOOK_HOST=WEBHOOK_HOST, + WEBHOOK_PORT=WEBHOOK_PORT, + WEBHOOK_URL_PATH=WEBHOOK_URL_PATH) -WEBAPP_HOST = os.environ.get('WEBAPP_HOST', 'localhost') -WEBAPP_PORT = os.environ.get('WEBAPP_PORT', 3001) +WEBHOOK_SSL_CERT = './webhook_cert.pem' -DATABASE_USER = os.environ.get('DATABASE_USER', 'postgres') -DATABASE_PASSWORD = os.environ.get('DATABASE_PASSWORD', 'postgres') -DATABASE_HOST = os.environ.get('DATABASE_HOST', 'localhost') +WEBAPP_HOST = os.environ.get('WEBAPP_HOST', '0.0.0.0') +WEBAPP_PORT = os.environ.get('WEBAPP_PORT', 7777) + +DATABASE_USER = os.environ.get('POSTGRES_USER', 'postgres') +DATABASE_PASSWORD = os.environ.get('POSTGRES_PASSWORD', 'postgres') +DATABASE_HOST = os.environ.get('DATABASE_HOST', 'db') DATABASE_PORT = os.environ.get('DATABASE_PORT', '5432') -DATABASE_NAME = os.environ.get('DATABASE_NAME', 'tgvkbot') +DATABASE_NAME = os.environ.get('POSTGRES_DB', 'tgvkbot') -VK_APP_ID = os.environ.get('VK_APP_ID', 1234567) +VK_APP_ID = os.environ.get('VK_APP_ID') -BOT_TOKEN = os.environ.get('BOT_TOKEN', '123456789:AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLL') +BOT_TOKEN = os.environ.get('BOT_TOKEN') SETTINGS_VAR = os.environ.get('SETTINGS_VAR', 'DJANGO_TGVKBOT_SETTINGS_MODULE') diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..cb5df37 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,45 @@ +version: '3' +services: + db: + image: "postgres:9.6.5" + volumes: + - "dbdata:/var/lib/postgresql/data" + env_file: + - env_file + restart: always + networks: + - db_nw + tgbot: + build: . + volumes: + - .:/src + env_file: + - env_file + command: bash -c "python manage.py makemigrations data && python manage.py migrate data && python telegram.py" + restart: always + networks: + - db_nw + - web_nw + depends_on: + - db + nginx: + image: "nginx:1.13.5" + ports: + - "8443:8443" + restart: always + volumes: + - .:/src + - ./conf.d:/etc/nginx/conf.d + env_file: + - env_file + networks: + - web_nw + depends_on: + - tgbot +networks: + db_nw: + driver: bridge + web_nw: + driver: bridge +volumes: + dbdata: \ No newline at end of file diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..2124aaa --- /dev/null +++ b/install.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +sudo apt-get update && sudo apt-get upgrade && \ +sudo apt-get install docker.io -y && \ +sudo usermod -aG docker $(whoami) && \ +sudo curl -L https://github.com/docker/compose/releases/download/1.20.1/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose && \ +sudo chmod +x /usr/local/bin/docker-compose && \ +python3 set_env.py && \ +python3 obtaincert.py && \ +sudo docker-compose build && \ +sudo docker-compose up -d \ No newline at end of file diff --git a/obtaincert.py b/obtaincert.py new file mode 100644 index 0000000..28dd32e --- /dev/null +++ b/obtaincert.py @@ -0,0 +1,35 @@ +from subprocess import call +from config import WEBHOOK_HOST + +OPENSSL_CONFIG_TEMPLATE = """ +prompt = no +distinguished_name = req_distinguished_name +req_extensions = v3_req +[ req_distinguished_name ] +C = RU +ST = Saint-Petersburg +L = Saint-Petersburg +O = tgvkbot +OU = tgvkbot +CN = %(domain)s +emailAddress = tgvkbot@gmail.com +[ v3_req ] +# Extensions to add to a certificate request +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +subjectAltName = @alt_names +[ alt_names ] +DNS.1 = %(domain)s +DNS.2 = *.%(domain)s +""" + +call([ + 'openssl', 'genrsa', '-out', 'webhook_pkey.pem', '2048' +]) +config = open('openssl_config', 'w') +config.write(OPENSSL_CONFIG_TEMPLATE % {'domain': WEBHOOK_HOST}) +config.close() +call([ + 'openssl', 'req', '-new', '-x509', '-days', '3650', '-key', 'webhook_pkey.pem', '-out', 'webhook_cert.pem', + '-config', 'openssl_config' +]) diff --git a/set_env.py b/set_env.py new file mode 100644 index 0000000..2f00213 --- /dev/null +++ b/set_env.py @@ -0,0 +1,64 @@ +from config import API_VERSION +from urllib.request import urlopen, Request +from urllib.error import HTTPError +from urllib.parse import urlencode + +ENV_FILE_TEMPLATE = """ +POSTGRES_DB=tgvkbot +BOT_TOKEN=%(tg_token)s +VK_APP_ID=%(vk_app_id)s +""" + + +def check_token(token): + response = urlopen("https://api.telegram.org/bot{token}/{method}".format(token=token, method='getMe')) + if response.code == 200: + return True + else: + raise HTTPError + + +def get_auth_page(app_id): + AUTH_URL = 'https://oauth.vk.com/authorize' + params = {'client_id': app_id, + 'redirect_uri': 'https://oauth.vk.com/blank.html', + 'display': 'mobile', + 'response_type': 'token', + 'v': API_VERSION + } + post_args = urlencode(params).encode('UTF-8') + request = Request(AUTH_URL, post_args) + response = urlopen(request) + if response.code == 200: + return True + else: + raise HTTPError + + +def set_env(): + while True: + tg_token = input('Telegram Token: ') + tg_token = tg_token.strip() + try: + check_token(tg_token) + break + except HTTPError: + print('Token is invalid, try again!') + + while True: + vk_app_id = input('VK APP ID: ') + vk_app_id = vk_app_id.strip() + try: + get_auth_page(vk_app_id) + break + except HTTPError: + print('VK APP ID is invalid, try again!') + + with open('env_file', 'w') as env_file: + env_file.write(ENV_FILE_TEMPLATE % {'tg_token': tg_token, 'vk_app_id': vk_app_id}) + + print('Success!') + + +if __name__ == '__main__': + set_env() diff --git a/telegram.py b/telegram.py index cda6b9f..977681f 100644 --- a/telegram.py +++ b/telegram.py @@ -860,16 +860,8 @@ async def handle_chat_migration(msg: types.Message): async def on_startup(app): - webhook = await bot.get_webhook_info() - - # If URL is bad - if webhook.url != WEBHOOK_URL: - # If URL doesnt match current - remove webhook - if not webhook.url: - await bot.delete_webhook() - - # Set new URL for webhook - await bot.set_webhook(WEBHOOK_URL) + # Set new URL for webhook + await bot.set_webhook(WEBHOOK_URL, certificate=open(WEBHOOK_SSL_CERT, 'rb')) if __name__ == '__main__': diff --git a/vk_messages.py b/vk_messages.py index 51b7379..511aa68 100644 --- a/vk_messages.py +++ b/vk_messages.py @@ -1,4 +1,4 @@ -from concurrent.futures._base import CancelledError +from concurrent.futures._base import CancelledError, TimeoutError from aiovk.longpoll import LongPoll @@ -869,7 +869,7 @@ async def vk_polling(vkuser: VkUser): lp = LongPoll(session, mode=10, version=4) while VkUser.objects.filter(token=vkuser.token, is_polling=True).exists(): data = await lp.wait() - log.debug('Longpoll: ' + str(data)) + log.warning('Longpoll: ' + str(data)) if data['updates']: for update in data['updates']: await process_longpoll_event(api, update) @@ -882,6 +882,9 @@ async def vk_polling(vkuser: VkUser): vkuser.is_polling = False vkuser.save() break + except TimeoutError: + log.warning('Polling timeout') + asyncio.sleep(5) except CancelledError: log.warning('Stopped polling for: id ' + str(vkuser.pk)) break