diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..99d3c80 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +ARG BUILD_FROM +FROM $BUILD_FROM + +RUN apk add --no-cache python3 + +COPY arkteos_proxy.py /arkteos_proxy.py +COPY run.sh /run.sh +RUN chmod +x /run.sh + +CMD ["/run.sh"] diff --git a/arkteos_proxy.py b/arkteos_proxy.py new file mode 100644 index 0000000..7ea98d7 --- /dev/null +++ b/arkteos_proxy.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python3 +# Proxy TCP pour PAC Arkteos REG3 +# Permet les connexions simultanées de Node-RED et de l'app mobile Arkteos +# Usage : arkteos_proxy.py + +import socket +import threading +import sys +import time +import logging + +# Configuration depuis les arguments (injectés par run.sh depuis les options HA) +PAC_HOST = sys.argv[1] if len(sys.argv) > 1 else "192.168.X.X" +PAC_PORT = int(sys.argv[2]) if len(sys.argv) > 2 else 9641 +PROXY_PORT = int(sys.argv[3]) if len(sys.argv) > 3 else 9641 + +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s %(levelname)s %(message)s', + stream=sys.stdout +) +logger = logging.getLogger(__name__) + +stop_event = threading.Event() +clients = [] +clients_lock = threading.Lock() + +def log(msg): + logger.info(msg) + +def connect_to_pac(): + while not stop_event.is_set(): + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.connect((PAC_HOST, PAC_PORT)) + log(f"Connecté à la PAC {PAC_HOST}:{PAC_PORT}") + return s + except Exception as e: + log(f"Échec connexion PAC : {e}. Nouvelle tentative dans 10s...") + time.sleep(10) + return None + +def pac_reader(pac_socket): + log("Démarrage thread lecture PAC") + try: + while not stop_event.is_set(): + try: + data = pac_socket.recv(4096) + except Exception as e: + log(f"Erreur lecture PAC : {e}") + break + if not data: + log("PAC a fermé la connexion") + break + + dead_clients = [] + with clients_lock: + for c in clients: + try: + c.sendall(data) + except Exception: + dead_clients.append(c) + + if dead_clients: + with clients_lock: + for c in dead_clients: + try: + c.close() + except Exception: + pass + if c in clients: + clients.remove(c) + log("Client déconnecté nettoyé") + finally: + log("Arrêt thread lecture PAC") + pac_socket.close() + +def pac_keepalive(pac_socket): + log("Démarrage thread keepalive PAC") + while not stop_event.is_set(): + time.sleep(300) + try: + pac_socket.sendall(b'\x00') + log("Keepalive envoyé à la PAC") + except Exception as e: + log(f"Erreur keepalive PAC : {e}") + break + log("Arrêt thread keepalive PAC") + +def handle_client(client_socket, client_addr, pac_socket): + log(f"Nouveau client : {client_addr}") + with clients_lock: + clients.append(client_socket) + try: + while not stop_event.is_set(): + try: + data = client_socket.recv(1024) + except Exception: + break + if not data: + break + try: + pac_socket.sendall(data) + except Exception as e: + log(f"Erreur envoi PAC depuis {client_addr} : {e}") + break + finally: + with clients_lock: + if client_socket in clients: + clients.remove(client_socket) + try: + client_socket.close() + except Exception: + pass + log(f"Client déconnecté : {client_addr}") + +def start_proxy(): + server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server_socket.bind(('0.0.0.0', PROXY_PORT)) + server_socket.listen(5) + log(f"Proxy en écoute sur 0.0.0.0:{PROXY_PORT}") + + pac_socket = connect_to_pac() + if pac_socket is None: + log("Impossible de se connecter à la PAC. Arrêt.") + return + + reader_thread = threading.Thread(target=pac_reader, args=(pac_socket,), daemon=True) + reader_thread.start() + + keepalive_thread = threading.Thread(target=pac_keepalive, args=(pac_socket,), daemon=True) + keepalive_thread.start() + + try: + while not stop_event.is_set(): + server_socket.settimeout(1) + try: + client_socket, addr = server_socket.accept() + t = threading.Thread(target=handle_client, args=(client_socket, addr, pac_socket), daemon=True) + t.start() + except socket.timeout: + pass + + # Reconnexion si la PAC s'est déconnectée + if not reader_thread.is_alive(): + log("Thread lecture PAC mort, reconnexion...") + with clients_lock: + for c in clients: + try: + c.close() + except Exception: + pass + clients.clear() + pac_socket = connect_to_pac() + if pac_socket is None: + break + reader_thread = threading.Thread(target=pac_reader, args=(pac_socket,), daemon=True) + reader_thread.start() + keepalive_thread = threading.Thread(target=pac_keepalive, args=(pac_socket,), daemon=True) + keepalive_thread.start() + except Exception as e: + log(f"Erreur serveur proxy : {e}") + finally: + stop_event.set() + server_socket.close() + try: + pac_socket.close() + except Exception: + pass + log("Proxy arrêté.") + +if __name__ == "__main__": + try: + start_proxy() + except KeyboardInterrupt: + log("Arrêt demandé") + stop_event.set() + sys.exit(0) diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000..c3abae4 --- /dev/null +++ b/config.yaml @@ -0,0 +1,20 @@ +name: Arkteos Proxy +version: "1.0.0" +slug: arkteos_proxy +description: Proxy TCP pour PAC Arkteos REG3 — permet les connexions simultanées de Node-RED et de l'app mobile Arkteos. +url: https://github.com/raph666/arkteos-proxy-addon +arch: + - aarch64 + - amd64 + - armhf + - armv7 + - i386 +init: false +options: + pac_host: "192.168.X.X" + pac_port: 9641 + proxy_port: 9641 +schema: + pac_host: str + pac_port: int + proxy_port: int diff --git a/repository.yaml b/repository.yaml new file mode 100644 index 0000000..e21d38d --- /dev/null +++ b/repository.yaml @@ -0,0 +1,3 @@ +name: Arkteos Proxy +url: https://github.com/raph666/arkteos-proxy-addon +maintainer: raph666 diff --git a/run.sh b/run.sh new file mode 100644 index 0000000..f521e37 --- /dev/null +++ b/run.sh @@ -0,0 +1,11 @@ +#!/usr/bin/with-contenv bashio + +PAC_HOST=$(bashio::config 'pac_host') +PAC_PORT=$(bashio::config 'pac_port') +PROXY_PORT=$(bashio::config 'proxy_port') + +bashio::log.info "Démarrage du proxy Arkteos" +bashio::log.info "PAC : ${PAC_HOST}:${PAC_PORT}" +bashio::log.info "Proxy en écoute sur port : ${PROXY_PORT}" + +exec python3 /arkteos_proxy.py "${PAC_HOST}" "${PAC_PORT}" "${PROXY_PORT}"