Grotti 3.0 Phase 1: Migrationen, Pool, Queue- und Abzug-Fixes.

Nummerierte SQL-Migrationen, psycopg-Pool, waitress, hängende Queue-Jobs,
sichere History-Abzüge, Chargen-Locks, Upload-/Host-Validierung, ProxyFix.
This commit is contained in:
Dominic Gundel committed 2026-10-18 00:47:23 +02:00
1 parent 9e22cebb81
commit 0236278929
19 files changed
+986 -81

No files matched your search

+73 -6
View File
@@ -1,19 +1,26 @@
"""Grotti starten. Kern und Seiten liegen daneben."""
import atexit
import signal
import threading
import time
from grotti import jobs, library, store
from waitress import serve
from grotti import db, jobs, library, store
from grotti.config import load
from grotti.db import connect, prepare
from web.app import create_app
def background(cfg):
def work():
try:
with connect(cfg["database_url"]) as conn:
with db.connect(cfg["database_url"]) as conn:
library.sync_watch(conn, cfg, force=False)
jobs.sync_history(conn, cfg)
deleted = db.prune_run_samples(conn, keep_days=90)
if deleted:
print("Proben gelöscht: %s" % deleted, flush=True)
conn.commit()
except Exception as exc:
print("Abgleich: %s" % exc, flush=True)
@@ -21,19 +28,79 @@ def background(cfg):
threading.Thread(target=work, name="grotti-abgleich", daemon=True).start()
def _retention_loop(cfg, stop_event):
"""Nächtlich alte run_samples entfernen."""
while not stop_event.wait(3600):
hour = time.localtime().tm_hour
if hour != 3:
continue
try:
with db.connect(cfg["database_url"]) as conn:
n = db.prune_run_samples(conn, keep_days=90)
conn.commit()
if n:
print("Retention: %s Proben gelöscht." % n, flush=True)
except Exception as exc:
print("Retention: %s" % exc, flush=True)
stop_event.wait(3600)
def main():
cfg = load()
cfg["data_dir"].mkdir(parents=True, exist_ok=True)
(cfg["data_dir"] / "eingang").mkdir(exist_ok=True)
prepare(cfg["database_url"])
with connect(cfg["database_url"]) as conn:
db.prepare(cfg["database_url"])
db.init_pool(cfg["database_url"], min_size=2, max_size=16)
with db.connect(cfg["database_url"]) as conn:
store.seed_printers(conn, cfg["printers_file"])
store.seed_materials(conn)
conn.commit()
app = create_app(cfg)
watcher = app.extensions["watcher"]
dispatcher = app.extensions["print_queue"]
send_queue = app.extensions["send_queue"]
watcher.start()
dispatcher.start()
if hasattr(send_queue, "start"):
send_queue.start()
stop_event = threading.Event()
retention = threading.Thread(
target=_retention_loop,
args=(cfg, stop_event),
name="grotti-retention",
daemon=True,
)
retention.start()
background(cfg)
def shutdown(*_args):
stop_event.set()
try:
watcher.stop()
except Exception:
pass
try:
dispatcher.stop()
except Exception:
pass
try:
if hasattr(send_queue, "stop"):
send_queue.stop()
except Exception:
pass
db.close_pool()
atexit.register(shutdown)
for sig in (signal.SIGINT, signal.SIGTERM):
try:
signal.signal(sig, lambda *_: shutdown())
except (ValueError, OSError):
pass
print("Grotti: http://%s:%s/" % (cfg["host"], cfg["port"]), flush=True)
app.run(host=cfg["host"], port=cfg["port"], threaded=True, use_reloader=False)
serve(app, host=cfg["host"], port=cfg["port"], threads=16, channel_timeout=120)
if __name__ == "__main__":