sonuprasad23 commited on
Commit
bb99b47
·
1 Parent(s): 36fd3e9
Files changed (3) hide show
  1. Dockerfile +18 -32
  2. server.py +30 -19
  3. worker.py +16 -55
Dockerfile CHANGED
@@ -5,57 +5,43 @@ WORKDIR /app
5
 
6
  ENV DEBIAN_FRONTEND=noninteractive
7
  ENV TZ=Etc/UTC
8
- ENV HOME=/home/appuser
9
- ENV PYTHONUSERBASE=/home/appuser/.local
10
 
11
- # HuggingFace Spaces specific optimizations
12
- ENV CHROME_BIN=/usr/bin/chromium
13
- ENV CHROMEDRIVER_PATH=/usr/bin/chromedriver
14
- ENV DISPLAY=:99
15
-
16
- # System deps + Chromium + ChromeDriver + essential libs for HF Spaces
17
  RUN apt-get update && apt-get install -y --no-install-recommends \
18
- wget \
19
- gnupg \
20
- unzip \
21
- ca-certificates \
22
  chromium \
23
  chromium-driver \
24
- libnss3 \
25
- libxss1 \
26
  libasound2 \
27
- libxtst6 \
28
  libatk-bridge2.0-0 \
 
29
  libgtk-3-0 \
 
 
 
 
30
  libgbm1 \
31
- fonts-liberation \
32
- libappindicator3-1 \
33
- libasound2 \
34
- libdbus-glib-1-2 \
35
- libdrm2 \
36
  libxcomposite1 \
37
  libxdamage1 \
38
- libxrandr2 \
39
- xvfb \
40
  procps \
41
  && rm -rf /var/lib/apt/lists/*
42
 
43
- # Create unprivileged user with proper permissions
44
- RUN addgroup --system appuser && adduser --system --ingroup appuser appuser
45
- RUN mkdir -p /home/appuser/.local /tmp/chrome-temp && \
46
- chown -R appuser:appuser /app /home/appuser /tmp/chrome-temp && \
47
- chmod 755 /tmp/chrome-temp
48
 
49
- USER appuser
50
 
51
- COPY --chown=appuser:appuser requirements.txt .
52
  RUN pip install --no-cache-dir --user -r requirements.txt
53
 
54
- ENV PATH="/home/appuser/.local/bin:${PATH}"
55
 
56
- COPY --chown=appuser:appuser . .
57
 
58
- # Create config directory if needed
59
  RUN mkdir -p config
60
 
61
  EXPOSE 7860
 
5
 
6
  ENV DEBIAN_FRONTEND=noninteractive
7
  ENV TZ=Etc/UTC
8
+ ENV HOME=/tmp/hf-user
9
+ ENV PYTHONUSERBASE=/tmp/hf-user/.local
10
 
11
+ # Install Chrome and dependencies for HuggingFace Spaces
 
 
 
 
 
12
  RUN apt-get update && apt-get install -y --no-install-recommends \
 
 
 
 
13
  chromium \
14
  chromium-driver \
15
+ ca-certificates \
16
+ fonts-liberation \
17
  libasound2 \
 
18
  libatk-bridge2.0-0 \
19
+ libdrm2 \
20
  libgtk-3-0 \
21
+ libnspr4 \
22
+ libnss3 \
23
+ libxss1 \
24
+ libxtst6 \
25
  libgbm1 \
26
+ libxrandr2 \
 
 
 
 
27
  libxcomposite1 \
28
  libxdamage1 \
 
 
29
  procps \
30
  && rm -rf /var/lib/apt/lists/*
31
 
32
+ # Create user directories
33
+ RUN mkdir -p /tmp/hf-user/.local && \
34
+ chmod 755 /tmp/hf-user /tmp/hf-user/.local
 
 
35
 
36
+ USER 1000
37
 
38
+ COPY --chown=1000:1000 requirements.txt .
39
  RUN pip install --no-cache-dir --user -r requirements.txt
40
 
41
+ ENV PATH="/tmp/hf-user/.local/bin:${PATH}"
42
 
43
+ COPY --chown=1000:1000 . .
44
 
 
45
  RUN mkdir -p config
46
 
47
  EXPOSE 7860
server.py CHANGED
@@ -24,13 +24,14 @@ load_dotenv()
24
 
25
  app = Flask(__name__)
26
  app.config['SECRET_KEY'] = 'secret-key-for-hillside-automation'
27
- FRONTEND_ORIGIN = os.getenv('FRONTEND_URL', 'http://127.0.0.1:5500')
28
- CORS(app, resources={r"/*": {"origins": FRONTEND_ORIGIN}})
29
- socketio = SocketIO(app, cors_allowed_origins=FRONTEND_ORIGIN, async_mode='eventlet')
 
 
30
 
31
  bot_instance = None
32
  session_data = {}
33
- automation_thread = None
34
 
35
  class EmailService:
36
  def __init__(self):
@@ -38,7 +39,8 @@ class EmailService:
38
  self.password = os.getenv('EMAIL_PASSWORD')
39
  self.smtp_server = "smtp.gmail.com"; self.smtp_port = 587
40
  if not self.sender_email or not self.password:
41
- print("[Email] WARNING: Email credentials not found in secrets.")
 
42
  def send_report(self, recipients, subject, body, attachments=None):
43
  if not self.sender_email or not self.password: return False
44
  try:
@@ -52,7 +54,8 @@ class EmailService:
52
  with smtplib.SMTP(self.smtp_server, self.smtp_port) as server:
53
  server.starttls(); server.login(self.sender_email, self.password); server.send_message(msg)
54
  print(f"Email sent successfully to {', '.join(recipients)}"); return True
55
- except Exception as e: print(f"Failed to send email: {e}"); return False
 
56
 
57
  class GoogleDriveService:
58
  def __init__(self):
@@ -62,12 +65,15 @@ class GoogleDriveService:
62
  from google.oauth2 import service_account
63
  from googleapiclient.discovery import build
64
  base64_creds = os.getenv('GDRIVE_SA_KEY_BASE64')
65
- if not base64_creds or not self.folder_id: raise ValueError("Google Drive secrets not found.")
 
66
  creds_json = base64.b64decode(base64_creds).decode('utf-8'); creds_dict = json.loads(creds_json)
67
  self.creds = service_account.Credentials.from_service_account_info(creds_dict, scopes=['https://www.googleapis.com/auth/drive'])
68
  self.service = build('drive', 'v3', credentials=self.creds)
69
- print("[G-Drive] Service initialized securely from secrets.")
70
- except Exception as e: print(f"[G-Drive] CRITICAL ERROR: Could not initialize Google Drive service: {e}")
 
 
71
  def upload_file(self, filename, file_content):
72
  if not self.service: return False
73
  try:
@@ -77,15 +83,18 @@ class GoogleDriveService:
77
  self.service.files().create(body=file_metadata, media_body=media, fields='id').execute()
78
  print(f"[G-Drive] File '{filename}' uploaded successfully.")
79
  return True
80
- except Exception as e: print(f"[G-Drive] ERROR: File upload failed: {e}"); return False
 
81
 
82
  email_service = EmailService()
83
  drive_service = GoogleDriveService()
84
 
85
  def get_email_list():
86
  try:
87
- with open('config/emails.conf', 'r') as f: return [line.strip() for line in f if line.strip()]
88
- except FileNotFoundError: return []
 
 
89
 
90
  def run_automation_process(session_id):
91
  global bot_instance
@@ -94,7 +103,7 @@ def run_automation_process(session_id):
94
  data = session_data.get(session_id, {}); csv_content = data.get('csv_content')
95
  df = pd.read_csv(io.StringIO(csv_content));
96
  if 'Status' not in df.columns: df.insert(1, 'Status', '')
97
- patient_list = df[ (df['Status'] != 'Done') & (df['Status'] != 'Bad') ]['Name'].tolist()
98
  socketio.emit('initial_stats', {'total': len(patient_list)})
99
  results = bot_instance.process_patient_list(patient_list)
100
  is_terminated = bot_instance.termination_event.is_set()
@@ -128,12 +137,13 @@ def generate_and_send_reports(session_id, results, is_crash_report=False, is_ter
128
 
129
  @app.route('/')
130
  def status_page():
131
- APP_STATUS_HTML = """<!DOCTYPE html><html lang="en"><head><title>API Status</title><style>body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;display:flex;justify-content:center;align-items:center;height:100vh;margin:0;background:#f0f2f5;}.status-box{text-align:center;padding:40px 60px;background:white;border-radius:12px;box-shadow:0 8px 30px rgba(0,0,0,0.1);}h1{font-size:24px;color:#333;margin-bottom:10px;} .indicator{font-size:18px;font-weight:600;padding:8px 16px;border-radius:20px;}.active{color:#28a745;background-color:#e9f7ea;}</style></head><body><div class="status-box"><h1>Hillside's Automation API</h1><div class="indicator active">● Active</div></div></body></html>"""
132
  return Response(APP_STATUS_HTML)
133
 
134
  @socketio.on('connect')
135
  def handle_connect():
136
- print(f'Frontend connected. Allowed origin: {FRONTEND_ORIGIN}'); emit('email_list', {'emails': get_email_list()})
 
137
 
138
  @socketio.on('initialize_session')
139
  def handle_init(data):
@@ -162,7 +172,8 @@ def handle_otp(data):
162
  if is_success:
163
  emit('login_successful')
164
  session_id = 'user_session'
165
- socketio.start_background_task(target=run_automation_process, args=(session_id,))
 
166
  else: emit('error', {'message': f'OTP failed: {error_message}'})
167
 
168
  @socketio.on('terminate_process')
@@ -171,8 +182,8 @@ def handle_terminate():
171
 
172
  if __name__ == '__main__':
173
  print("====================================================================")
174
- print(" Hillside Automation Backend - PRODUCTION READY")
175
- print(" Make sure all secrets are set in your Hugging Face Space.")
176
- print(" Listening on http://127.0.0.1:5000")
177
  print("====================================================================")
178
  socketio.run(app, host='0.0.0.0', port=int(os.getenv('PORT', 7860)))
 
24
 
25
  app = Flask(__name__)
26
  app.config['SECRET_KEY'] = 'secret-key-for-hillside-automation'
27
+
28
+ # Configure CORS for both local development and your Netlify frontend
29
+ FRONTEND_ORIGIN = os.getenv('FRONTEND_URL', 'https://quantbot.netlify.app')
30
+ CORS(app, resources={r"/*": {"origins": [FRONTEND_ORIGIN, "http://localhost:3000", "http://127.0.0.1:5500"]}})
31
+ socketio = SocketIO(app, cors_allowed_origins=[FRONTEND_ORIGIN, "http://localhost:3000", "http://127.0.0.1:5500"], async_mode='eventlet')
32
 
33
  bot_instance = None
34
  session_data = {}
 
35
 
36
  class EmailService:
37
  def __init__(self):
 
39
  self.password = os.getenv('EMAIL_PASSWORD')
40
  self.smtp_server = "smtp.gmail.com"; self.smtp_port = 587
41
  if not self.sender_email or not self.password:
42
+ print("[Email] WARNING: Email credentials not found in environment variables.")
43
+
44
  def send_report(self, recipients, subject, body, attachments=None):
45
  if not self.sender_email or not self.password: return False
46
  try:
 
54
  with smtplib.SMTP(self.smtp_server, self.smtp_port) as server:
55
  server.starttls(); server.login(self.sender_email, self.password); server.send_message(msg)
56
  print(f"Email sent successfully to {', '.join(recipients)}"); return True
57
+ except Exception as e:
58
+ print(f"Failed to send email: {e}"); return False
59
 
60
  class GoogleDriveService:
61
  def __init__(self):
 
65
  from google.oauth2 import service_account
66
  from googleapiclient.discovery import build
67
  base64_creds = os.getenv('GDRIVE_SA_KEY_BASE64')
68
+ if not base64_creds or not self.folder_id:
69
+ raise ValueError("Google Drive secrets not found.")
70
  creds_json = base64.b64decode(base64_creds).decode('utf-8'); creds_dict = json.loads(creds_json)
71
  self.creds = service_account.Credentials.from_service_account_info(creds_dict, scopes=['https://www.googleapis.com/auth/drive'])
72
  self.service = build('drive', 'v3', credentials=self.creds)
73
+ print("[G-Drive] Service initialized securely from environment variables.")
74
+ except Exception as e:
75
+ print(f"[G-Drive] CRITICAL ERROR: Could not initialize Google Drive service: {e}")
76
+
77
  def upload_file(self, filename, file_content):
78
  if not self.service: return False
79
  try:
 
83
  self.service.files().create(body=file_metadata, media_body=media, fields='id').execute()
84
  print(f"[G-Drive] File '{filename}' uploaded successfully.")
85
  return True
86
+ except Exception as e:
87
+ print(f"[G-Drive] ERROR: File upload failed: {e}"); return False
88
 
89
  email_service = EmailService()
90
  drive_service = GoogleDriveService()
91
 
92
  def get_email_list():
93
  try:
94
+ with open('config/emails.conf', 'r') as f:
95
+ return [line.strip() for line in f if line.strip()]
96
+ except FileNotFoundError:
97
+ return []
98
 
99
  def run_automation_process(session_id):
100
  global bot_instance
 
103
  data = session_data.get(session_id, {}); csv_content = data.get('csv_content')
104
  df = pd.read_csv(io.StringIO(csv_content));
105
  if 'Status' not in df.columns: df.insert(1, 'Status', '')
106
+ patient_list = df[(df['Status'] != 'Done') & (df['Status'] != 'Bad')]['Name'].tolist()
107
  socketio.emit('initial_stats', {'total': len(patient_list)})
108
  results = bot_instance.process_patient_list(patient_list)
109
  is_terminated = bot_instance.termination_event.is_set()
 
137
 
138
  @app.route('/')
139
  def status_page():
140
+ APP_STATUS_HTML = """<!DOCTYPE html><html lang="en"><head><title>Hillside Automation API - HuggingFace</title><style>body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;display:flex;justify-content:center;align-items:center;height:100vh;margin:0;background:#f0f2f5;}.status-box{text-align:center;padding:40px 60px;background:white;border-radius:12px;box-shadow:0 8px 30px rgba(0,0,0,0.1);}h1{font-size:24px;color:#333;margin-bottom:10px;} .indicator{font-size:18px;font-weight:600;padding:8px 16px;border-radius:20px;}.active{color:#28a745;background-color:#e9f7ea;}.info{margin-top:20px;color:#666;}</style></head><body><div class="status-box"><h1>🤗 Hillside Automation API</h1><div class="indicator active">● Running on HuggingFace Spaces</div><div class="info">Frontend: <a href="https://quantbot.netlify.app" target="_blank">quantbot.netlify.app</a></div></div></body></html>"""
141
  return Response(APP_STATUS_HTML)
142
 
143
  @socketio.on('connect')
144
  def handle_connect():
145
+ print(f'Frontend connected from: {FRONTEND_ORIGIN}')
146
+ emit('email_list', {'emails': get_email_list()})
147
 
148
  @socketio.on('initialize_session')
149
  def handle_init(data):
 
172
  if is_success:
173
  emit('login_successful')
174
  session_id = 'user_session'
175
+ # Fixed syntax for background task
176
+ socketio.start_background_task(run_automation_process, session_id)
177
  else: emit('error', {'message': f'OTP failed: {error_message}'})
178
 
179
  @socketio.on('terminate_process')
 
182
 
183
  if __name__ == '__main__':
184
  print("====================================================================")
185
+ print(" 🤗 Hillside Automation Backend - HuggingFace Spaces")
186
+ print(f" Frontend URL: {FRONTEND_ORIGIN}")
187
+ print(f" Port: {os.getenv('PORT', 7860)}")
188
  print("====================================================================")
189
  socketio.run(app, host='0.0.0.0', port=int(os.getenv('PORT', 7860)))
worker.py CHANGED
@@ -1,9 +1,6 @@
1
  # worker.py
2
  import time
3
  import threading
4
- import tempfile
5
- import shutil
6
- import os
7
  import subprocess
8
 
9
  from selenium import webdriver
@@ -22,15 +19,14 @@ class QuantumBot:
22
  self.driver = None
23
  self.DEFAULT_TIMEOUT = 30
24
  self.termination_event = threading.Event()
25
- self.temp_user_dir = None
26
 
27
- def _kill_existing_chrome_processes(self):
28
- """Kill any existing Chrome processes that might be locking directories"""
29
  try:
30
- subprocess.run(['pkill', '-f', 'chrome'], capture_output=True, text=True)
31
- subprocess.run(['pkill', '-f', 'chromium'], capture_output=True, text=True)
32
- time.sleep(2)
33
- except Exception:
34
  pass
35
 
36
  def initialize_driver(self):
@@ -38,53 +34,32 @@ class QuantumBot:
38
  self.micro_status("Initializing headless browser...")
39
 
40
  # Kill any existing Chrome processes first
41
- self._kill_existing_chrome_processes()
42
-
43
- # Create a completely isolated temporary directory for this session
44
- self.temp_user_dir = tempfile.mkdtemp(prefix="hf-chrome-", dir="/tmp")
45
- os.chmod(self.temp_user_dir, 0o755)
46
 
47
  options = ChromeOptions()
 
48
 
49
- # Force Chrome to use our isolated temporary directory
50
- options.add_argument(f"--user-data-dir={self.temp_user_dir}")
51
- options.add_argument(f"--data-path={self.temp_user_dir}")
52
- options.add_argument(f"--disk-cache-dir={self.temp_user_dir}/cache")
53
 
54
- # HuggingFace Spaces specific optimizations
55
  options.add_argument("--headless=new")
56
  options.add_argument("--no-sandbox")
57
  options.add_argument("--disable-dev-shm-usage")
58
  options.add_argument("--disable-gpu")
59
- options.add_argument("--disable-software-rasterizer")
60
- options.add_argument("--disable-background-timer-throttling")
61
- options.add_argument("--disable-backgrounding-occluded-windows")
62
- options.add_argument("--disable-renderer-backgrounding")
63
- options.add_argument("--disable-features=TranslateUI,BlinkGenPropertyTrees")
64
  options.add_argument("--remote-debugging-port=0")
 
65
  options.add_argument("--no-first-run")
66
- options.add_argument("--no-default-browser-check")
67
  options.add_argument("--disable-extensions")
68
- options.add_argument("--disable-plugins")
69
- options.add_argument("--disable-images")
70
- options.add_argument("--disable-javascript")
71
- options.add_argument("--window-size=1920,1080")
72
 
73
- # Set user agent
74
  user_agent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
75
  options.add_argument(f"--user-agent={user_agent}")
76
 
77
- # Set binary location for HF Spaces
78
- options.binary_location = "/usr/bin/chromium"
79
-
80
  service = ChromeService(executable_path="/usr/bin/chromedriver")
81
-
82
- # Set service args for additional stability
83
- service.service_args = ["--verbose", "--whitelisted-ips="]
84
-
85
  self.driver = webdriver.Chrome(service=service, options=options)
86
 
87
- # Reduce webdriver detection
88
  self.driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
89
  'source': "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"
90
  })
@@ -94,11 +69,6 @@ class QuantumBot:
94
  except Exception as e:
95
  error_message = f"Message: {str(e)}"
96
  print(f"CRITICAL ERROR in WebDriver Initialization: {error_message}")
97
-
98
- # Cleanup on failure
99
- if self.temp_user_dir and os.path.exists(self.temp_user_dir):
100
- shutil.rmtree(self.temp_user_dir, ignore_errors=True)
101
-
102
  return False, error_message
103
 
104
  def micro_status(self, message):
@@ -244,16 +214,7 @@ class QuantumBot:
244
  try:
245
  if self.driver:
246
  self.driver.quit()
247
-
248
- # Kill any remaining Chrome processes
249
- self._kill_existing_chrome_processes()
250
-
251
- # Clean up temporary directory
252
- if self.temp_user_dir and os.path.exists(self.temp_user_dir):
253
- shutil.rmtree(self.temp_user_dir, ignore_errors=True)
254
- print(f"[Bot] Cleaned up temporary Chrome profile: {self.temp_user_dir}")
255
-
256
  except Exception as e:
257
  print(f"[Bot] Error during shutdown: {e}")
258
-
259
- print("[Bot] Chrome session closed.")
 
1
  # worker.py
2
  import time
3
  import threading
 
 
 
4
  import subprocess
5
 
6
  from selenium import webdriver
 
19
  self.driver = None
20
  self.DEFAULT_TIMEOUT = 30
21
  self.termination_event = threading.Event()
 
22
 
23
+ def _kill_chrome_processes(self):
24
+ """Kill any existing Chrome processes to prevent conflicts"""
25
  try:
26
+ subprocess.run(['pkill', '-f', 'chrome'], capture_output=True, text=True, timeout=5)
27
+ subprocess.run(['pkill', '-f', 'chromium'], capture_output=True, text=True, timeout=5)
28
+ time.sleep(1)
29
+ except:
30
  pass
31
 
32
  def initialize_driver(self):
 
34
  self.micro_status("Initializing headless browser...")
35
 
36
  # Kill any existing Chrome processes first
37
+ self._kill_chrome_processes()
 
 
 
 
38
 
39
  options = ChromeOptions()
40
+ options.binary_location = "/usr/bin/chromium"
41
 
42
+ # DON'T set --user-data-dir - let Chrome handle it automatically
43
+ # This prevents the "already in use" error
 
 
44
 
45
+ # Minimal required flags for HuggingFace Spaces
46
  options.add_argument("--headless=new")
47
  options.add_argument("--no-sandbox")
48
  options.add_argument("--disable-dev-shm-usage")
49
  options.add_argument("--disable-gpu")
 
 
 
 
 
50
  options.add_argument("--remote-debugging-port=0")
51
+ options.add_argument("--window-size=1920,1080")
52
  options.add_argument("--no-first-run")
 
53
  options.add_argument("--disable-extensions")
 
 
 
 
54
 
55
+ # User agent for better compatibility
56
  user_agent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
57
  options.add_argument(f"--user-agent={user_agent}")
58
 
 
 
 
59
  service = ChromeService(executable_path="/usr/bin/chromedriver")
 
 
 
 
60
  self.driver = webdriver.Chrome(service=service, options=options)
61
 
62
+ # Anti-detection
63
  self.driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
64
  'source': "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"
65
  })
 
69
  except Exception as e:
70
  error_message = f"Message: {str(e)}"
71
  print(f"CRITICAL ERROR in WebDriver Initialization: {error_message}")
 
 
 
 
 
72
  return False, error_message
73
 
74
  def micro_status(self, message):
 
214
  try:
215
  if self.driver:
216
  self.driver.quit()
217
+ self._kill_chrome_processes()
218
+ print("[Bot] Chrome session closed and cleaned up.")
 
 
 
 
 
 
 
219
  except Exception as e:
220
  print(f"[Bot] Error during shutdown: {e}")