ernestmindres commited on
Commit
48ef31f
·
verified ·
1 Parent(s): c751eed

Upload terminal_manager.py

Browse files
Files changed (1) hide show
  1. terminal_manager.py +139 -0
terminal_manager.py ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # terminal_manager.py
2
+
3
+ import os
4
+ import pty
5
+ import sys
6
+ import subprocess
7
+ from threading import Thread
8
+ from flask import request
9
+ from flask_socketio import SocketIO, emit
10
+
11
+ # Dictionnaire pour stocker les processus PTY actifs par ID de session SocketIO
12
+ active_terminals = {}
13
+
14
+ def read_and_send_output(sid, master_fd, socketio):
15
+ """
16
+ Fonction cible du Thread : Lit en continu le PTY et émet la sortie au client.
17
+ """
18
+ while True:
19
+ try:
20
+ # Lire les données du PTY (la taille de lecture peut être ajustée)
21
+ # Utilisation de 'errors=ignore' pour éviter les problèmes d'encodage binaires
22
+ output = os.read(master_fd, 1024).decode(errors='ignore')
23
+
24
+ if output:
25
+ # Émettre les données lues uniquement au client concerné
26
+ socketio.emit('terminal_output', {'output': output}, room=sid)
27
+ else:
28
+ # Si os.read retourne 0, le processus est terminé
29
+ break
30
+ except OSError:
31
+ # Le descripteur de fichier est fermé (par le disconnect handler)
32
+ break
33
+ except Exception as e:
34
+ # Journalisation de l'erreur
35
+ print(f"Erreur de lecture/écriture du terminal pour {sid}: {e}", file=sys.stderr)
36
+ break
37
+
38
+ # Nettoyage à la fin du thread
39
+ cleanup_terminal(sid, socketio)
40
+
41
+
42
+ def cleanup_terminal(sid, socketio):
43
+ """
44
+ Fonction de nettoyage pour le processus PTY.
45
+ """
46
+ if sid in active_terminals:
47
+ # Tuer le processus du shell s'il est toujours en cours
48
+ try:
49
+ pid = active_terminals[sid]['pid']
50
+ os.kill(pid, 9) # Envoyer un SIGKILL pour s'assurer qu'il s'arrête
51
+ except OSError:
52
+ pass # Le processus est déjà terminé
53
+
54
+ # Fermer le descripteur de fichier maître
55
+ try:
56
+ os.close(active_terminals[sid]['master_fd'])
57
+ except OSError:
58
+ pass
59
+
60
+ del active_terminals[sid]
61
+
62
+ # Informer le client que la connexion est perdue
63
+ socketio.emit('terminal_output', {'output': '\r\n--- Session terminal terminée ---\r\n'}, room=sid)
64
+ print(f"Nettoyage complet du terminal pour la session {sid}", file=sys.stderr)
65
+
66
+
67
+ def setup_terminal_events(socketio: SocketIO):
68
+ """
69
+ Initialise et enregistre les gestionnaires d'événements SocketIO pour le terminal.
70
+ """
71
+
72
+ @socketio.on('connect')
73
+ def handle_connect():
74
+ sid = request.sid
75
+ try:
76
+ # 1. Créer le Pseudo-Terminal (master/slave)
77
+ master_fd, slave_fd = pty.openpty()
78
+
79
+ # 2. Définir la commande du shell à lancer (ex: /bin/bash sur Linux)
80
+ # Pour l'exécution de code Python, on pourrait aussi utiliser 'python' ou 'jupyter console'
81
+ shell_command = os.environ.get('SHELL', '/bin/bash')
82
+
83
+ # 3. Forker le processus pour lancer le shell
84
+ pid = os.fork()
85
+ if pid == 0:
86
+ # Processus enfant (le shell)
87
+ os.close(master_fd) # Fermer le master
88
+ # Dupliquer le slave vers stdin, stdout, stderr
89
+ os.dup2(slave_fd, sys.stdin.fileno())
90
+ os.dup2(slave_fd, sys.stdout.fileno())
91
+ os.dup2(slave_fd, sys.stderr.fileno())
92
+ os.close(slave_fd)
93
+
94
+ # Exécuter le shell, en s'assurant qu'il est exécuté
95
+ os.execv(shell_command, [shell_command])
96
+ os._exit(0) # Sécurité si execv échoue
97
+ else:
98
+ # Processus parent (le serveur)
99
+ os.close(slave_fd) # Fermer le slave
100
+
101
+ # Stocker l'information du terminal actif
102
+ active_terminals[sid] = {'pid': pid, 'master_fd': master_fd}
103
+
104
+ # 4. Lancer le thread de lecture et d'émission de sortie
105
+ thread = Thread(target=read_and_send_output, args=(sid, master_fd, socketio))
106
+ thread.daemon = True # Permet au thread de s'arrêter lorsque le programme principal s'arrête
107
+ thread.start()
108
+
109
+ emit('terminal_output', {'output': '\r\nTerminal connecté. Tapez votre code ou une commande.\r\n'})
110
+
111
+ except Exception as e:
112
+ # Échec de l'initialisation du terminal
113
+ print(f"Erreur d'initialisation du terminal: {e}", file=sys.stderr)
114
+ emit('terminal_error', {'message': f'Erreur de connexion au terminal: {e}'})
115
+
116
+ @socketio.on('disconnect')
117
+ def handle_disconnect():
118
+ """
119
+ Gère la déconnexion d'un client.
120
+ """
121
+ sid = request.sid
122
+ cleanup_terminal(sid, socketio)
123
+
124
+ @socketio.on('terminal_input')
125
+ def handle_terminal_input(data):
126
+ """
127
+ Reçoit l'entrée utilisateur du client et l'écrit dans le PTY.
128
+ """
129
+ sid = request.sid
130
+ if sid in active_terminals:
131
+ master_fd = active_terminals[sid]['master_fd']
132
+ input_data = data.get('input', '')
133
+ try:
134
+ # Écrire l'entrée utilisateur dans le PTY (comme si l'utilisateur tapait)
135
+ os.write(master_fd, input_data.encode())
136
+ except OSError as e:
137
+ # Si le descripteur de fichier est fermé
138
+ print(f"Erreur d'écriture dans le PTY (processus terminé ?): {e}", file=sys.stderr)
139
+ emit('terminal_output', {'output': '\r\nErreur de communication avec le terminal.'})