-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathapp.py
More file actions
118 lines (95 loc) · 4.08 KB
/
Copy pathapp.py
File metadata and controls
118 lines (95 loc) · 4.08 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
"""
app.py - Flask application entry point.
Modules:
models.py - data classes
database.py - SQLite persistence (users, workflows, executions, state)
settings.py - settings + stable secret key
auth.py - authentication, admin bootstrap, MFA, request gate
executor.py - execution engine + bounded concurrency dispatcher
scheduler.py - cleanup, scheduled firing, auto-backup
routes/ - auth, workflows, executions, tools, settings
"""
import logging
import os
from datetime import timedelta
from flask import Flask, send_from_directory
import auth
from database import init_db, reconcile_interrupted_executions
from executor import start_dispatcher
from scheduler import start_background_threads
import metrics
from settings import get_or_create_secret_key, load_settings
_LOG_FMT = "%(asctime)s %(levelname)s %(name)s - %(message)s"
def _configure_logging(debug: bool = False) -> None:
root = logging.getLogger()
root.setLevel(logging.DEBUG if debug else logging.INFO)
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG if debug else logging.INFO)
ch.setFormatter(logging.Formatter(_LOG_FMT))
root.addHandler(ch)
os.makedirs("logs", exist_ok=True)
try:
from logging.handlers import RotatingFileHandler
fh = RotatingFileHandler("logs/app.log", maxBytes=2_000_000, backupCount=5, encoding="utf-8")
fh.setLevel(logging.DEBUG)
fh.setFormatter(logging.Formatter(_LOG_FMT))
root.addHandler(fh)
except Exception as e:
logging.warning(f"File logging unavailable: {e}")
def create_app() -> Flask:
app = Flask(__name__, static_folder=None)
app.secret_key = get_or_create_secret_key()
app.config["MAX_CONTENT_LENGTH"] = 16 * 1024 * 1024
app.config["PERMANENT_SESSION_LIFETIME"] = timedelta(hours=12)
app.config["SESSION_COOKIE_HTTPONLY"] = True
app.config["SESSION_COOKIE_SAMESITE"] = "Lax"
for d in ("logs", "backups", "img"):
os.makedirs(d, exist_ok=True)
init_db()
# Ensure settings exist in the DB (also migrates any legacy app_settings.json).
from settings import ensure_persisted
ensure_persisted()
# Bootstrap the admin account (prints temp credentials on first run).
auth.bootstrap_admin()
# Recover from an unclean shutdown: mark mid-flight executions interrupted.
try:
n = reconcile_interrupted_executions()
if n:
logging.getLogger(__name__).warning(f"Marked {n} interrupted execution(s) from previous run")
except Exception as e:
logging.getLogger(__name__).warning(f"Reconciliation failed: {e}")
# Authentication gate (before_request).
auth.register(app)
# Routes
from routes.auth_routes import register as reg_auth
from routes.workflows import register as reg_workflows
from routes.executions import register as reg_executions
from routes.tools import register as reg_tools
from routes.settings_routes import register as reg_settings
from routes.health import register as reg_health
reg_auth(app)
reg_workflows(app)
reg_executions(app)
reg_tools(app)
reg_settings(app)
reg_health(app)
@app.route("/img/<path:filename>")
def serve_image(filename):
return send_from_directory("img", filename)
# Expose the current user to all templates (for the header).
@app.context_processor
def _inject_user():
return {"current_user": auth.current_user()}
start_dispatcher()
start_background_threads()
metrics.start_global_sampler()
return app
if __name__ == "__main__":
_configure_logging(debug=load_settings().get("enable_debug_mode", False))
application = create_app()
# Bind to localhost by default; this tool executes shell commands and must
# not be exposed to the network. Override with RECKONING_HOST if you know
# what you are doing (e.g. an isolated host behind other controls).
host = os.environ.get("RECKONING_HOST", "127.0.0.1")
port = int(os.environ.get("RECKONING_PORT", "5000"))
application.run(debug=False, host=host, port=port)