-
-
Notifications
You must be signed in to change notification settings - Fork 44
Expand file tree
/
Copy pathmain.py
More file actions
190 lines (161 loc) · 6.59 KB
/
Copy pathmain.py
File metadata and controls
190 lines (161 loc) · 6.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
#!/usr/bin/env python3
import os
import sys
import subprocess
import platform
import time
import socket
import signal
import threading
# --- CONFIGURATION ---
# Path to the directory containing the docker-compose.yml file
VM_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "app", "gui"))
# The main python command to run after setup (args added at runtime)
PYTHON_APP_BASE_CMD = [sys.executable, "-m", "app.main"]
# Service readiness check
READY_HOST = "localhost"
READY_PORT = 3001
MAX_WAIT_SECONDS = 60
# ---------------------
# --- HELPER FUNCTIONS ---
def run_command(
cmd: list,
cwd: str = None,
check: bool = True,
capture: bool = False,
quiet: bool = False,
) -> subprocess.CompletedProcess:
"""Helper to run subprocess commands robustly."""
try:
use_shell = platform.system() == "Windows"
# Always capture output when quiet mode is enabled
should_capture = capture or quiet
result = subprocess.run(
cmd,
cwd=cwd,
check=check,
shell=use_shell,
stdout=subprocess.PIPE if should_capture else sys.stdout,
stderr=subprocess.PIPE if should_capture else sys.stderr,
text=True if should_capture else False,
)
return result
except subprocess.CalledProcessError as e:
print(f"\n[ERROR] Command failed: {' '.join(cmd)}")
if capture or quiet:
print(f"STDOUT:\n{e.stdout}\nSTDERR:\n{e.stderr}")
raise
except FileNotFoundError:
print(f"\n[ERROR] Command executable not found: {cmd[0]}")
raise
def is_port_open(host: str, port: int, timeout: int = 1) -> bool:
"""Checks if a TCP port is open on a given host."""
try:
with socket.create_connection((host, port), timeout=timeout):
return True
except (socket.timeout, ConnectionRefusedError, OSError):
return False
# --- MAIN LOGIC ---
def main():
# === IGNORE CTRL+C ===
# Tell this Python wrapper script to completely ignore SIGINT (Ctrl+C).
# It will not raise KeyboardInterrupt. It will just keep doing what it's doing.
# The child process will still receive the signal from the terminal driver.
if threading.current_thread() is threading.main_thread():
signal.signal(signal.SIGINT, signal.SIG_IGN)
# ------------------------------
# Check if GUI mode is enabled
# [V1.2.2] GUI mode is temporarily disabled - always force False
gui_mode_enabled = False # os.getenv("GUI_MODE_ENABLED", "False").lower() == "true"
docker_started = False
# Check if browser startup UI is active (suppress verbose output)
browser_startup_ui = os.getenv("BROWSER_STARTUP_UI", "0") == "1"
if not browser_startup_ui:
print("--- Starting Launch Sequence ---")
final_exit_code = 0
# === TRY BLOCK: Setup and Run ===
try:
# 1. Start Docker VM (only if GUI mode is enabled)
if gui_mode_enabled:
if not browser_startup_ui:
print("\n[1/3] Launching VM Docker containers in background...")
if not os.path.isdir(VM_DIR):
print(f"[ERROR] Docker directory not found: {VM_DIR}")
sys.exit(1)
run_command(["docker", "compose", "up", "-d"], cwd=VM_DIR)
docker_started = True
# 2. Wait Loop
if not browser_startup_ui:
print(
f"\n[2/3] Waiting for VM service to be ready on port {READY_PORT}..."
)
waited = 0
while not is_port_open(READY_HOST, READY_PORT):
if waited >= MAX_WAIT_SECONDS:
print(f"\n[ERROR] Timed out waiting for VM port {READY_PORT}.")
raise TimeoutError(
f"Service on port {READY_PORT} did not become ready."
)
if not browser_startup_ui:
print(".", end="", flush=True)
time.sleep(1)
waited += 1
if not browser_startup_ui:
print(f"\n[OK] VM Service is reachable after {waited}s!")
# 3. Start Python Agent
if not browser_startup_ui:
print("\n[3/3] Launching Python Agent...")
else:
if not browser_startup_ui:
print("\n[1/1] Launching Python Agent (CLI Mode)...")
if not browser_startup_ui:
print("--------------------------------")
print("Type '/exit' or use your defined quit hotkey to stop.")
print("--------------------------------")
# Run the main Python app in the foreground.
# This call BLOCKS until the app exits.
if getattr(sys, "frozen", False):
# PyInstaller binary: import and run directly instead of subprocess
# (sys.executable points to the binary, not Python)
from app.main import main as app_main
app_main()
final_exit_code = 0
else:
python_app_cmd = PYTHON_APP_BASE_CMD + sys.argv[1:]
result = subprocess.run(
python_app_cmd,
stdin=sys.stdin,
stdout=sys.stdout,
stderr=sys.stderr,
check=False,
)
final_exit_code = result.returncode
except (subprocess.CalledProcessError, TimeoutError, FileNotFoundError) as e:
import traceback
print(f"\n[main] {type(e).__name__}: {e}")
traceback.print_exc()
final_exit_code = 1
except Exception as e:
import traceback
print(f"\n[main] Unhandled {type(e).__name__}: {e}")
traceback.print_exc()
final_exit_code = 1
# === FINALLY BLOCK: Guaranteed Cleanup ===
# This block runs only when the 'try' block finishes naturally or hits a non-signal error.
finally:
print(f"\n\n--- Cleanup Initiated (Exit Status: {final_exit_code}) ---")
# 1. Stop Docker containers (only if started)
if docker_started:
print("[*] Stopping Docker VM containers...")
try:
run_command(["docker", "compose", "down"], cwd=VM_DIR, check=False)
except Exception as e:
print(f"[!] Warning: Error during docker shutdown: {e}")
# No port sweep afterwards: `compose down` stops exactly the
# containers we started. Killing whatever else listens on the
# port would hit processes CraftBot never launched.
else:
print("[*] Skipping Docker cleanup (not started in CLI mode).")
sys.exit(final_exit_code)
if __name__ == "__main__":
main()