import os import subprocess import threading import socket import socketserver import time import sys # --- CONFIGURAZIONE (DA ADATTARE ALLE TUE ESIGENZE) --- # Queste variabili dovrebbero essere configurate in un file di configurazione separato # o passate come argomenti al tuo script Python. TCPSERVER_PID_FILE = "/var/run/my_socket_server.pid" TCPSERVER_IP = "0.0.0.0" # Ascolta su tutte le interfacce TCPSERVER_PORT = 12345 # Porta del server socket TCPSERVER_USER = "admin" # Credenziali opzionali per l'autenticazione TCPSERVER_PWD = "password123" # Variabili usate internamente dallo script Bash, che in Python diventano parametri o logica RUN_FROM_TCPSERVER = False # Sarà True quando il comando è eseguito dal server socket # Definisci i percorsi per i comandi esterni (se non sono nel PATH di sistema) # In un ambiente di produzione, è meglio specificare percorsi assoluti. # Ad esempio, TR_COMMAND = "/usr/bin/tr" TR_COMMAND = "tr" CUT_COMMAND = "cut" READLINK_COMMAND = "readlink" # --- FUNZIONI UTILITY (PLACEHOLDERS) --- # Queste sono le funzioni che erano richiamate dallo script Bash # Dovrai implementarle in Python o collegarle alle tue librerie esistenti. def log_write(source, level, message): """Placeholder per la funzione di logging.""" timestamp = time.strftime("%Y-%m-%d %H:%M:%S") print(f"[{timestamp}] [{source}] [{level.upper()}] {message}") def json_error(code, message): """Placeholder per la funzione che genera un JSON di errore.""" print(f'{{"status": "error", "code": {code}, "message": "{message}"}}') def json_status(*args): """Placeholder per la funzione che genera un JSON di stato.""" if args: print(f'{{"status": "success", "data": "{", ".join(args)}"}}') else: print('{"status": "success"}') def message_write(level, message): """Placeholder per la funzione che scrive un messaggio.""" print(f"[{level.upper()}] {message}") def ev_open(alias, param=None): """Placeholder per la funzione di apertura valvola.""" log_write("socket_server", "info", f"Executing ev_open for alias: {alias}, param: {param}") # Qui andrebbe la logica per aprire la valvola pass def ev_open_in(arg2, arg3, arg4, arg5): """Placeholder per la funzione di apertura valvola in un intervallo.""" log_write("socket_server", "info", f"Executing ev_open_in with args: {arg2}, {arg3}, {arg4}, {arg5}") # Qui andrebbe la logica per aprire la valvola in un intervallo pass def ev_close(alias): """Placeholder per la funzione di chiusura valvola.""" log_write("socket_server", "info", f"Executing ev_close for alias: {alias}") # Qui andrebbe la logica per chiudere la valvola pass def close_all(): """Placeholder per la funzione di chiusura di tutte le valvole.""" log_write("socket_server", "info", "Executing close_all") # Qui andrebbe la logica per chiudere tutte le valvole pass def cron_disable_all_open_close(): """Placeholder per la funzione di disabilitazione scheduling cron.""" log_write("socket_server", "info", "Executing cron_disable_all_open_close") # Qui andrebbe la logica per disabilitare lo scheduling pass def cron_enable_all_open_close(): """Placeholder per la funzione di abilitazione scheduling cron.""" log_write("socket_server", "info", "Executing cron_enable_all_open_close") # Qui andrebbe la logica per abilitare lo scheduling pass def set_cron_init(): """Placeholder per la funzione set_cron_init.""" log_write("socket_server", "info", "Executing set_cron_init") return "" # Simula output vuoto per successo def set_cron_start_socket_server(): """Placeholder per la funzione set_cron_start_socket_server.""" log_write("socket_server", "info", "Executing set_cron_start_socket_server") return "" def set_cron_check_rain_sensor(): """Placeholder per la funzione set_cron_check_rain_sensor.""" log_write("socket_server", "info", "Executing set_cron_check_rain_sensor") return "" def set_cron_check_rain_online(): """Placeholder per la funzione set_cron_check_rain_online.""" log_write("socket_server", "info", "Executing set_cron_check_rain_online") return "" def set_cron_close_all_for_rain(): """Placeholder per la funzione set_cron_close_all_for_rain.""" log_write("socket_server", "info", "Executing set_cron_close_all_for_rain") return "" def del_cron_open(arg): """Placeholder per la funzione del_cron_open.""" log_write("socket_server", "info", f"Executing del_cron_open with arg: {arg}") return "" def del_cron_open_in(arg): """Placeholder per la funzione del_cron_open_in.""" log_write("socket_server", "info", f"Executing del_cron_open_in with arg: {arg}") return "" def del_cron_close(arg): """Placeholder per la funzione del_cron_close.""" log_write("socket_server", "info", f"Executing del_cron_close with arg: {arg}") return "" def add_cron_open(arg2, arg3, arg4, arg5, arg6, arg7, arg8): """Placeholder per la funzione add_cron_open.""" log_write("socket_server", "info", f"Executing add_cron_open with args: {arg2}, {arg3}, {arg4}, {arg5}, {arg6}, {arg7}, {arg8}") return "" def add_cron_close(arg2, arg3, arg4, arg5, arg6, arg7, arg8): """Placeholder per la funzione add_cron_close.""" log_write("socket_server", "info", f"Executing add_cron_close with args: {arg2}, {arg3}, {arg4}, {arg5}, {arg6}, {arg7}, {arg8}") return "" def cmd_pigardensched(arg2, arg3, arg4, arg5, arg6): """Placeholder per la funzione cmd_pigardensched.""" log_write("socket_server", "info", f"Executing cmd_pigardensched with args: {arg2}, {arg3}, {arg4}, {arg5}, {arg6}") return "" def reset_last_rain_sensor_timestamp(): """Placeholder per la funzione reset_last_rain_sensor_timestamp.""" log_write("socket_server", "info", "Executing reset_last_rain_sensor_timestamp") pass def reset_last_rain_online_timestamp(): """Placeholder per la funzione reset_last_rain_online_timestamp.""" log_write("socket_server", "info", "Executing reset_last_rain_online_timestamp") pass def sensor_status_set(arg2, arg3, arg4): """Placeholder per la funzione sensor_status_set.""" log_write("socket_server", "info", f"Executing sensor_status_set with args: {arg2}, {arg3}, {arg4}") pass # --- FUNZIONI DI GESTIONE DEI PROCESSI --- def list_descendants(pid): """ Simula la logica di 'list_descendants' Bash. In un sistema reale, dovresti usare librerie come 'psutil' per ottenere i processi figli. Qui è solo una simulazione. """ try: # psutil è l'opzione migliore, se disponibile # import psutil # parent = psutil.Process(pid) # return [child.pid for child in parent.children(recursive=True)] # Fallback semplice per simulazione (non accurato per processi reali) # Se non hai psutil, questa funzione è difficile da replicare accuratamente in modo generico. # Potrebbe essere necessario un approccio specifico per il tuo sistema operativo. return [] # Nessun discendente noto senza psutil except Exception as e: log_write("process_manager", "error", f"Errore nel listing dei discendenti di {pid}: {e}") return [] def get_script_path(): """Restituisce il percorso assoluto dello script corrente.""" return os.path.abspath(sys.argv[0]) # --- CLASSE GESTORE RICHIESTE SOCKET --- class MyTCPHandler(socketserver.BaseRequestHandler): """ La classe MyTCPHandler gestirà ogni nuova connessione client. Sovrascrive il metodo handle() per implementare la logica del server. """ def handle(self): global RUN_FROM_TCPSERVER RUN_FROM_TCPSERVER = True # TCPREMOTEIP in Python è self.client_address[0] client_ip = self.client_address[0] log_write("socket_server", "info", f"Nuova connessione da: {client_ip}") try: # Autenticazione (se configurata) if TCPSERVER_USER and TCPSERVER_PWD: # Leggi utente (timeout 3 secondi) self.request.settimeout(3) try: user_line = self.request.recv(1024).decode('utf-8').strip() password_line = self.request.recv(1024).decode('utf-8').strip() except socket.timeout: log_write("socket_server", "warning", f"socket connection from: {client_ip} - Timeout during credentials read") json_error(0, "Authentication timeout") return if user_line != TCPSERVER_USER or password_line != TCPSERVER_PWD: log_write("socket_server", "warning", f"socket connection from: {client_ip} - Bad socket server credentials - user:{user_line}") self.request.sendall(f'{{"status": "error", "code": 0, "message": "Bad socket server credentials"}}\n'.encode('utf-8')) return else: log_write("socket_server", "info", f"socket connection from: {client_ip} - Authentication successful") # Leggi il comando command_line = self.request.recv(4096).decode('utf-8').strip() # Parsing del comando args = command_line.split(' ') arg1 = args[0] if len(args) > 0 else "" arg2 = args[1] if len(args) > 1 else "" arg3 = args[2] if len(args) > 2 else "" arg4 = args[3] if len(args) > 3 else "" arg5 = args[4] if len(args) > 4 else "" arg6 = args[5] if len(args) > 5 else "" arg7 = args[6] if len(args) > 6 else "" arg8 = args[7] if len(args) > 7 else "" log_write("socket_server", "info", f"socket connection from: {client_ip} - command: {command_line}") # Esegui il comando response = self.execute_command(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) # Invia la risposta al client (assumendo che le funzioni json_error/json_status stampino su stdout) # Potresti voler catturare l'output di quelle funzioni e inviarlo qui. # Per semplicità, qui si presuppone che inviino direttamente al client, # ma in un sistema reale dovresti costruire la risposta JSON e inviarla. self.request.sendall(response.encode('utf-8') + b'\n') except socket.timeout: log_write("socket_server", "warning", f"socket connection from: {client_ip} - Timeout waiting for command.") self.request.sendall(f'{{"status": "error", "code": 0, "message": "Timeout waiting for command"}}\n'.encode('utf-8')) except Exception as e: log_write("socket_server", "error", f"Errore durante la gestione della connessione da {client_ip}: {e}") self.request.sendall(f'{{"status": "error", "code": -1, "message": "Internal server error"}}\n'.encode('utf-8')) finally: self.request.close() # Chiudi la connessione RUN_FROM_TCPSERVER = False # Reset per la prossima connessione o per operazioni interne def execute_command(self, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8): """Esegue il comando ricevuto dal socket server.""" # Questo metodo replicherà la logica della case statement Bash vret = "" # Per catturare i risultati delle funzioni if arg1 == "status": json_status(arg2, arg3, arg4, arg5, arg6, arg7) elif arg1 == "open": if not arg2: # "empty$arg2" == "empty" json_error(0, "Alias solenoid not specified") else: ev_open(arg2, arg3) # &> /dev/null è gestito non catturando l'output json_status(f"get_cron_open_in:{arg2}") elif arg1 == "open_in": ev_open_in(arg2, arg3, arg4, arg5) json_status(f"get_cron_open_in:{arg4}") elif arg1 == "close": if not arg2: json_error(0, "Alias solenoid not specified") else: ev_close(arg2) json_status(f"get_cron_open_in:{arg2}") elif arg1 == "close_all": if arg2 == "disable_scheduling": cron_disable_all_open_close() close_all() message_write("success", "All solenoid closed") json_status() elif arg1 == "cron_enable_all_open_close": cron_enable_all_open_close() message_write("success", "All solenoid enabled") json_status() elif arg1 == "set_general_cron": # Per i comandi di set_general_cron, chiami le funzioni e concateni i risultati # Ho ipotizzato che i risultati vuoti indichino successo e stringhe non vuote errori funcs_to_call = { "set_cron_init": set_cron_init, "set_cron_start_socket_server": set_cron_start_socket_server, "set_cron_check_rain_sensor": set_cron_check_rain_sensor, "set_cron_check_rain_online": set_cron_check_rain_online, "set_cron_close_all_for_rain": set_cron_close_all_for_rain, } # Qui si itera sugli argomenti come nello script Bash for i_arg in [arg for arg in [arg2, arg3, arg4, arg5, arg6, arg7] if arg]: if i_arg in funcs_to_call: ret_val = funcs_to_call[i_arg]() if ret_val: # Se la funzione restituisce qualcosa (un errore in Bash) vret += ret_val if vret: # Se c'è stato qualche errore in una delle chiamate json_error(0, "Cron set failed") log_write("socket_server", "error", f"Cron set failed: {vret}") else: message_write("success", "Cron set successful") json_status() elif arg1 == "del_cron_open": vret = del_cron_open(arg2) if vret: json_error(0, "Cron set failed") log_write("socket_server", "error", f"Cron del failed: {vret}") else: message_write("success", "Cron set successful") json_status() elif arg1 == "del_cron_open_in": vret = del_cron_open_in(arg2) if vret: json_error(0, "Cron del failed") log_write("socket_server", "error", f"Cron del failed: {vret}") else: message_write("success", "Scheduled start successfully deleted") json_status(f"get_cron_open_in:{arg2}") elif arg1 == "del_cron_close": vret = del_cron_close(arg2) if vret: json_error(0, "Cron set failed") log_write("socket_server", "error", f"Cron set failed: {vret}") else: message_write("success", "Cron set successful") json_status() elif arg1 == "add_cron_open": vret = add_cron_open(arg2, arg3, arg4, arg5, arg6, arg7, arg8) if vret: json_error(0, "Cron set failed") log_write("socket_server", "error", f"Cron set failed: {vret}") else: message_write("success", "Cron set successful") json_status() elif arg1 == "add_cron_close": vret = add_cron_close(arg2, arg3, arg4, arg5, arg6, arg7, arg8) if vret: json_error(0, "Cron set failed") log_write("socket_server", "error", f"Cron set failed: {vret}") else: message_write("success", "Cron set successful") json_status() elif arg1 == "cmd_pigardensched": vret = cmd_pigardensched(arg2, arg3, arg4, arg5, arg6) if vret: json_error(0, "piGardenSched command failed") log_write("socket_server", "error", f"piGardenSched command failed: {vret}") else: message_write("success", "Schedule set successful") json_status() elif arg1 == "reboot": message_write("warning", "System reboot is started") json_status() current_script_path = get_script_path() # Esegui il reboot in un sottoprocesso separato per non bloccare il server # e con nohup-like behavior. # Questo è un esempio, la gestione di reboot/poweroff in Python è delicata. # Potresti voler chiamare un comando di sistema come `sudo reboot`. subprocess.Popen([current_script_path, "reboot_system_internal_cmd"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, preexec_fn=os.setsid) # setsid per disassociare dal gruppo di processi elif arg1 == "poweroff": message_write("warning", "System shutdown is started") json_status() current_script_path = get_script_path() subprocess.Popen([current_script_path, "poweroff_system_internal_cmd"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, preexec_fn=os.setsid) # setsid per disassociare dal gruppo di processi elif arg1 == "reset_last_rain_sensor_timestamp": reset_last_rain_sensor_timestamp() message_write("success", "Timestamp of last sensor rain successful reset") json_status() elif arg1 == "reset_last_rain_online_timestamp": reset_last_rain_online_timestamp() message_write("success", "Timestamp of last online rain successful reset") json_status() elif arg1 == "sensor_status_set": if not arg2: json_error(0, "Alias sensor not specified") else: sensor_status_set(arg2, arg3, arg4) json_status() else: json_error(0, "invalid command") # Le funzioni json_error, json_status, message_write stampano direttamente su stdout # In un contesto di server reale, dovresti catturare il loro output e inviarlo al client. # Per questa conversione, sto assumendo che tu voglia replicare il comportamento Bash # di "stampa e poi la pipeline gestisce l'invio". Potrebbe richiedere un refactoring # delle funzioni json_error/status per restituire stringhe invece di stampare. # Per ora, restituisco una stringa vuota o un placeholder. return "" # Potresti voler restituire il JSON o il messaggio qui # --- FUNZIONI PRINCIPALI DEL SERVER --- def start_socket_server(): """Avvia il server socket.""" # Rimuovi il file PID esistente if os.path.exists(TCPSERVER_PID_FILE): os.remove(TCPSERVER_PID_FILE) # Scrivi il PID dello script principale nel file PID # In un sistema reale, dovresti scrivere il PID del processo del server, # che potrebbe essere diverso se usi un manager di processi come systemd. current_pid = os.getpid() with open(TCPSERVER_PID_FILE, "w") as f: f.write(str(current_pid)) log_write("socket_server", "info", f"Server PID {current_pid} scritto in {TCPSERVER_PID_FILE}") try: # Crea il server TCP # ThreadingTCPServer gestisce ogni richiesta in un thread separato with socketserver.ThreadingTCPServer((TCPSERVER_IP, TCPSERVER_PORT), MyTCPHandler) as server: log_write("socket_server", "info", f"Server socket avviato su {TCPSERVER_IP}:{TCPSERVER_PORT}") # Avvia il server, rimarrà in ascolto indefinitamente server.serve_forever() except Exception as e: log_write("socket_server", "error", f"Errore all'avvio del server socket: {e}") if os.path.exists(TCPSERVER_PID_FILE): os.remove(TCPSERVER_PID_FILE) # Pulisci il PID file in caso di errore sys.exit(1) def stop_socket_server(): """Ferma il server socket.""" if not os.path.exists(TCPSERVER_PID_FILE): print("Daemon is not running") sys.exit(1) log_write("socket_server", "info", "stop socket server") with open(TCPSERVER_PID_FILE, "r") as f: try: pid = int(f.read().strip()) except ValueError: print(f"Errore: Il file PID '{TCPSERVER_PID_FILE}' contiene un PID non valido.") sys.exit(1) # Tentativo di killare i processi discendenti (se list_descendants è implementato) descendants = list_descendants(pid) for d_pid in descendants: try: os.kill(d_pid, 9) # SIGKILL log_write("socket_server", "info", f"Terminato processo discendente {d_pid}") except ProcessLookupError: pass # Il processo non esiste # Tenta di killare il processo principale del server try: os.kill(pid, 9) # SIGKILL log_write("socket_server", "info", f"Terminato processo server {pid}") except ProcessLookupError: print(f"Processo con PID {pid} non trovato.") except Exception as e: log_write("socket_server", "error", f"Errore durante l'uccisione del processo server {pid}: {e}") # Rimuovi il file PID if os.path.exists(TCPSERVER_PID_FILE): os.remove(TCPSERVER_PID_FILE) log_write("socket_server", "info", "File PID rimosso.") # --- FUNZIONE PRINCIPALE PER LA GESTIONE DEGLI ARGOMENTI DELLA CLI --- if __name__ == "__main__": if len(sys.argv) > 1: command = sys.argv[1] if command == "start_socket_server": # Per avviare in background come un vero daemon, dovresti implementare # la demonizzazione (forking) qui o usare una libreria come `python-daemon`. # Per ora, questo lo avvia nel terminale corrente. log_write("main", "info", "Richiesta di avvio del socket server.") start_socket_server() elif command == "stop_socket_server": log_write("main", "info", "Richiesta di stop del socket server.") stop_socket_server() elif command == "socket_server_command": # Questa parte dovrebbe essere gestita dal server TCP stesso. # Non viene chiamata direttamente dalla riga di comando in Python in questo modo. # Se hai bisogno di testare la logica 'socket_server_command' isolatamente, # dovresti chiamare MyTCPHandler.execute_command() con argomenti di test. print("Errore: 'socket_server_command' non può essere chiamato direttamente come script.") print("Questa logica è gestita internamente dal server TCP.") sys.exit(1) elif command == "reboot_system_internal_cmd": # Comando interno per il riavvio effettivo log_write("main", "warning", "Eseguo il riavvio del sistema...") # In un sistema Linux, questo è il modo per riavviare subprocess.run(["sudo", "reboot"]) # Assicurati che l'utente che esegue lo script abbia i permessi sudo senza password per 'reboot' elif command == "poweroff_system_internal_cmd": # Comando interno per lo spegnimento effettivo log_write("main", "warning", "Eseguo lo spegnimento del sistema...") # In un sistema Linux, questo è il modo per spegnere subprocess.run(["sudo", "poweroff"]) # Assicurati che l'utente che esegue lo script abbia i permessi sudo senza password per 'poweroff' else: print(f"Comando non riconosciuto: {command}") print("Utilizzo: python your_script_name.py [start_socket_server|stop_socket_server]") sys.exit(1) else: print("Nessun comando specificato.") print("Utilizzo: python your_script_name.py [start_socket_server|stop_socket_server]") sys.exit(1)