Во второй части был описан клиент, который отправляет данные на внешний сервер.
Теперь опишем сервис, который на внешнем сервере принимает данные.
Я настраивал сервис на Debian Bullseye. Чистая установка. Наборы пакетов: SSH-сервер и Стандартные системные утилиты.
Сервис будет запущен от отдельного пользователя gps. Для этого добавим нового пользователя:
useradd -m -s /usr/sbin/nologin gps
В домашней директории нового пользователя создадим поддиректории, в которых будут лежать наш скрипт, файл конфигурации и логи:
mkdir /home/gps/gpsTracksReceiver mkdir /home/gps/gpsTracksReceiver/conf mkdir /home/gps/gpsTracksReceiver/log
Для записи логов нужны права на запись для нового пользователя gps или он должен быть владельцем директории:
chown gps:gps /home/gps/gpsTracksReceiver/log
Сам скрипт server.py:
import base64 import socket import os import pymysql import logging import logging.handlers from cryptography.fernet import Fernet from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from _thread import * from socket import error, SHUT_RDWR from conf import config as cfg log_level = logging.INFO # уровень логирования. пока реализован только INFO # Загрузка данных в SQL базу def insettData2SQL(clname, db, poid, plon, plat, palt, pspeed, podate, potime): mydb = pymysql.connect( host=cfg.mysql_server, user=cfg.mysql_user, passwd=cfg.mysql_pass, database=cfg.mysql_db ) mycursor = mydb.cursor() query = "INSERT INTO " + cfg.mysql_table + " (client, dbid, pid, lon, lat, alt, speed, pdate, ptime) " query = query + "VALUES ('" + clname + "', '" + db + "', " + poid + ", " + plon + ", " + plat + ", " + palt + ", " + pspeed + ", " + podate + ", " + potime + ");" mycursor.execute(query) mydb.commit() mydb.close() # Шифрование отправляемых данных def encryptData(clientName, rawData): kdf = PBKDF2HMAC( algorithm=hashes.SHA256(), length=32, salt=bytes(cfg.salt, encoding='utf8'), iterations=390000, ) key = base64.urlsafe_b64encode(kdf.derive(bytes(cfg.clients[clientName], encoding='utf8'))) cipher = Fernet(key) return cipher.encrypt(bytes(rawData, encoding='utf8')) # Расшифровка полученных данных def decryptData(clientName, encdata): kdf = PBKDF2HMAC( algorithm=hashes.SHA256(), length=32, salt=bytes(cfg.salt, encoding='utf8'), iterations=390000, ) key = base64.urlsafe_b64encode(kdf.derive(bytes(cfg.clients[clientName], encoding='utf8'))) cipher = Fernet(key) # Расшифровываем данные. Если не расшифровывается, то возвращаем error. try: decrData = cipher.decrypt(bytes(encdata, encoding='utf8')) return decrData except Exception as e: return bytes('error', encoding='utf8') # Подключение клиента к серверу def clientConnected(client, claddr): while True: # Получаем данные вида: клиент;зашифрованные-данные try: encData = str(client.recv (4096), encoding = 'utf8') except Exception as e: encData = 'Непонятный набор символов' # Если нет ";" считаем, что пришли некорректные данные if encData.find(';')<0: if encData == 'bye': logging.info('Клиент '+str(claddr)+' отключился') client.shutdown(SHUT_RDWR) client.close() break if encData: logging.info('Клиент '+str(claddr)+' передал неверные данные:'+encData) break # Отделяем имя клиента от зашифрованных данных clientname, Encdata = encData.split(';',1) # Я решил, что имя клиента не должно превышать 32 символа. if len(clientname)>32: logging.info('Клиент '+str(claddr)+' передал слишком длинное имя') break # Расшифруем данные data = str(decryptData(clientname, Encdata), encoding = 'utf8') # Если вернулось error, то считаем, что клиент передал неверные данные. if data == 'error': logging.info('Клиент '+str(claddr)+' передал неверные данные') break #track, id, longitude, latitude, altitude, speed, data, vremya, upload if data.count(',')!=8: logging.info('Клиент '+str(claddr)+' передал неверное количество параметров') break dbid, pid, lon, lat, alt, speed, pdate, ptime, upload = data.split(',') dbid = str(dbid).replace('(','') pid = str(pid).strip() lon = str(lon).strip() lat = str(lat).strip() alt = str(alt).strip() speed = str(speed).strip() pdate = str(pdate).strip() ptime = str(ptime).strip() insettData2SQL(str(clientname), dbid, pid, lon, lat, alt, speed, pdate, ptime) client.send(encryptData(clientname, "OK")) logging.info('Клиент '+str(claddr)+' отключён') client.shutdown(SHUT_RDWR) client.close() def log_setup(): log_handler = logging.handlers.WatchedFileHandler(cfg.log_file) formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s') log_handler.setFormatter(formatter) logger = logging.getLogger() logger.addHandler(log_handler) logger.setLevel(log_level) #START log_setup() serv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) serv.bind((cfg.server_address, cfg.server_port)) serv.listen(5) logging.info('Сервер запущен. Ожидает подключений на: '+cfg.server_address+':'+str(cfg.server_port)) try: while True: conn, addr = serv.accept() logging.info('Подключен клиент с адресом: '+str(addr)) start_new_thread(clientConnected, (conn, addr)) except (KeyboardInterrupt, SystemExit): logging.info("Остановка сервера") serv.close() finally: logging.info("Сервер остановлен")
Конфиг /home/gps/gpsTracksReceiver/conf/config.py:
clients = {"client": "58_btTe_ZJMm41gFP_XL6HgX7kT7vo9ywQzY_xRg6ZybrAfd", "client1": "I741xNK89PJ7SEtauZPnCsOsfeuWxS4akW-XIPbL68U9rg5i"} # Клиенты и их пароли salt = "Ld0GH14L1pv5itZb" # соль — это просто случайные данные, которые вы используете в качестве дополнения в вашем хеше, с целью усложнения расшифровки пароля mysql_server = "127.0.0.1" # адрес mysql сервера mysql_user = "dbuser" # пользователь с правами на запись в базу mysql_pass = "dbpass" # пароль этого пользователя mysql_db = "gpsdb" # имя базы mysql_table = "track" # таблица в базе server_address = "127.0.0.1" # адрес, на котором сервер принимает подключения от клиентов server_port = 3333 # порт, на котором сервер принимает подключения от клиентов log_file = "/home/gps/gpsTracksReceiver/log" # лог файл
clients — это myname и mykey из второй части. salt тоже из второй части.
Параметры mysql_ — это настройки подключения к Mysql (MariaDB) серверу, где будут храниться получаемые данные.
server_address и server_port — это, соответственно, адрес и порт на котором вышеописанный скрипт будет ожидать данные.
log_file — в какой файл будет писаться лог. Скрипт сам создаст файл, но нужно создать директорию (/var/log/gps/) с правами на запись для пользователя, от которого будет запускаться вышеописанный скрипт (в нашем случае пользователь gps).
Для запуска скрипта нужно доустановить пакет:
apt install python3-pymysql
он подтянет за собой python3-cryptography
Настройку SQL сервера MariaDB описывать не буду. В интернете полно мануалов.
Таблица в базе создаётся так:
CREATE TABLE track ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, client VARCHAR(32) NOT NULL, dbid VARCHAR(14) NOT NULL, pid INT NOT NULL, lon VARCHAR(32) NOT NULL, lat VARCHAR(32) NOT NULL, alt FLOAT NOT NULL, speed FLOAT NOT NULL, pdate DATE NOT NULL, ptime TIME NOT NULL, upload DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP() );
Создаём сервис для автоматического запуска server.py от пользователя gps.
Файл /etc/systemd/system/gpsTracksReceiver.service:
[Unit] Description=GPS Tracks Receiver After=multi-user.target [Service] User=gps Type=simple Restart=always ExecStart=/usr/bin/python3 /home/gps/gpsTracksReceiver/server.py [Install] WantedBy=multi-user.target
Установка сервиса:
systemctl daemon-reload systemctl enable gpsTracksReceiver.service
Запуск сервиса:
systemctl start gpsTracksReceiver.service
Остановка сервиса:
systemctl stop gpsTracksReceiver.service