# app.py (v2.1 - Final für Docker) from flask import Flask, jsonify, request import subprocess import sys import os import logging from datetime import datetime from pyngrok import ngrok, conf from config import Config logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') app = Flask(__name__) # Der Pfad, in dem sich der Code im Container befindet # (muss mit WORKDIR im Dockerfile übereinstimmen) APP_DIR = "/code" SCRIPT_MAP = { "run_duplicate_check": {"script": "duplicate_checker.py", "args": []}, "run_reclassify_branches": {"script": "brancheneinstufung2.py", "args": ["--mode", "reclassify_branches"]}, # ... fügen Sie hier bei Bedarf brancheneinstufung2.py hinzu } def setup_ngrok(): """Konfiguriert und startet den ngrok-Tunnel.""" try: authtoken = os.environ.get("NGROK_AUTHTOKEN") if not authtoken: if os.path.exists("ngrok_authtoken.txt"): with open("ngrok_authtoken.txt", "r") as f: authtoken = f.read().strip() if not authtoken: logging.error("NGROK_AUTHTOKEN nicht gefunden. Tunnel kann nicht gestartet werden.") return None conf.get_default().auth_token = authtoken public_url = ngrok.connect(8080, "http") logging.info(f"!!! Ngrok-Tunnel gestartet: {public_url} !!!") logging.info("!!! Bitte diese URL im Google Apps Script eintragen. !!!") return public_url except Exception as e: logging.error(f"Fehler beim Starten von ngrok: {e}") sys.exit(1) @app.route('/run-script', methods=['POST']) def run_script(): """ Startet ein Skript als Hintergrundprozess. Finale, robuste Version mit absoluten Pfaden. """ action = "unknown" try: data = request.get_json() action = data.get('action') if not action or action not in SCRIPT_MAP: return jsonify({"status": "error", "message": "Ungültige Aktion."}), 400 script_config = SCRIPT_MAP[action] script_name = script_config["script"] # ENTSCHEIDENDER FIX: Finde den Pfad relativ zu DIESER app.py Datei # __file__ ist der Pfad zur aktuell laufenden Datei (app.py) # os.path.dirname(__file__) ist der Ordner, in dem app.py liegt (/app im Container) script_dir = os.path.dirname(os.path.abspath(__file__)) script_path = os.path.join(script_dir, script_name) if not os.path.exists(script_path): logging.error(f"Skript unter dem Pfad '{script_path}' NICHT GEFUNDEN.") return jsonify({"status": "error", "message": f"Server-Fehler: Skript '{script_name}' nicht gefunden."}), 500 logging.info(f"Aktion '{action}' empfangen. Starte Skript: '{script_path}'...") python_executable = sys.executable command = [python_executable, script_path] + script_config["args"] log_dir = os.path.join(script_dir, "Log") os.makedirs(log_dir, exist_ok=True) timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") process_log_path = os.path.join(log_dir, f"{timestamp}_{action}.log") with open(process_log_path, 'w') as log_file: subprocess.Popen( command, # Wir müssen das Arbeitsverzeichnis nicht mehr setzen, da wir absolute Pfade verwenden stdout=log_file, stderr=log_file ) return jsonify({"status": "success", "message": f"Aktion '{action}' gestartet. Log wird in '{process_log_path}' geschrieben."}), 200 except Exception as e: logging.error(f"Kritischer Fehler in der run_script-Route bei Aktion '{action}': {e}", exc_info=True) return jsonify({"status": "error", "message": f"Server-Fehler: {str(e)}"}), 500 @app.route('/get-status', methods=['GET']) def get_status(): """Liest alle Job-Status-Dateien und gibt den aktuellen Stand zurück.""" all_statuses = [] for filename in os.listdir(STATUS_DIR): if filename.endswith(".json"): try: with open(os.path.join(STATUS_DIR, filename), 'r') as f: status_data = json.load(f) status_data['job_id'] = filename.replace('.json', '') all_statuses.append(status_data) except Exception as e: logging.warning(f"Konnte Statusdatei {filename} nicht lesen: {e}") # Sortiere nach Startzeit, neuester Job zuerst all_statuses.sort(key=lambda x: x.get('start_time', ''), reverse=True) return jsonify(all_statuses) if __name__ == '__main__': setup_ngrok() app.run(host='0.0.0.0', port=8080)