Gate watchdogs on integration configuration

This commit is contained in:
root 2026-08-14 18:02:32 +00:00
parent 947225fe0d
commit 4b62583937
6 changed files with 129 additions and 18 deletions

View file

@ -50,6 +50,21 @@ def _check_database():
return {'connected': False, 'error': str(e)} return {'connected': False, 'error': str(e)}
def _check_integration_watchdog(process_name, integration, start_time_threshold=None):
"""Un watchdog non configure est volontairement arrete et reste sain."""
try:
from watchdog_gate import integration_is_configured
if not integration_is_configured(integration):
return {'running': False, 'enabled': False}
except Exception as exc:
return {'running': False, 'enabled': False, 'configuration_error': str(exc)}
status = _check_watchdog(process_name, start_time_threshold=start_time_threshold)
status['enabled'] = True
return status
@health_bp.route('/') @health_bp.route('/')
def health(): def health():
"""Endpoint public de healthcheck.""" """Endpoint public de healthcheck."""
@ -58,14 +73,18 @@ def health():
# Le watchdog pronote met parfois plus de temps a demarrer (connexion ENT) # Le watchdog pronote met parfois plus de temps a demarrer (connexion ENT)
watchdogs = { watchdogs = {
'flask': _check_watchdog('gunicorn'), 'flask': _check_watchdog('gunicorn'),
'gmao_watchdog': _check_watchdog('gmao_watchdog.py'), 'gmao_watchdog': _check_integration_watchdog('gmao_watchdog.py', 'outlook'),
'ent_watchdog': _check_watchdog('ent_watchdog.py'), 'ent_watchdog': _check_integration_watchdog('ent_watchdog.py', 'ent'),
'pronote_watchdog': _check_watchdog('pronote_watchdog.py', start_time_threshold=WATCHDOG_STARTUP_GRACE_SECONDS), 'pronote_watchdog': _check_integration_watchdog(
'watchdog_dnd': _check_watchdog('watchdog_dnd.py'), 'pronote_watchdog.py',
'pronote',
start_time_threshold=WATCHDOG_STARTUP_GRACE_SECONDS,
),
'watchdog_dnd': _check_integration_watchdog('watchdog_dnd.py', 'yeastar'),
} }
all_watchdogs_ok = all( all_watchdogs_ok = all(
w.get('running') or w.get('starting') w.get('enabled') is False or w.get('running') or w.get('starting')
for w in watchdogs.values() for w in watchdogs.values()
) )
healthy = db_status.get('connected') and all_watchdogs_ok and not any(w.get('starting') for w in watchdogs.values()) healthy = db_status.get('connected') and all_watchdogs_ok and not any(w.get('starting') for w in watchdogs.values())

View file

@ -722,13 +722,13 @@ def api_save_auto_interpret():
if gmao_context.auto_interpret_enabled: if gmao_context.auto_interpret_enabled:
# Relancer GMAO watchdog # Relancer GMAO watchdog
gmao_script = '/app/gmao_watchdog.py' gmao_script = '/app/gmao_watchdog.py'
subprocess.Popen(['python3', gmao_script], subprocess.Popen(['python3', '/app/watchdog_gate.py', 'outlook', gmao_script],
stdout=subprocess.DEVNULL, stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL) stderr=subprocess.DEVNULL)
# Relancer ENT watchdog # Relancer ENT watchdog
ent_script = '/app/ent_watchdog.py' ent_script = '/app/ent_watchdog.py'
subprocess.Popen(['python3', ent_script], subprocess.Popen(['python3', '/app/watchdog_gate.py', 'ent', ent_script],
stdout=subprocess.DEVNULL, stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL) stderr=subprocess.DEVNULL)
@ -913,7 +913,7 @@ def api_pronote_watchdog_restart():
# Démarrer le nouveau # Démarrer le nouveau
subprocess.Popen( subprocess.Popen(
['python3', 'pronote_watchdog.py'], ['python3', '/app/watchdog_gate.py', 'pronote', '/app/pronote_watchdog.py'],
cwd=workdir, cwd=workdir,
stdout=open('/tmp/pronote_watchdog.out', 'a'), stdout=open('/tmp/pronote_watchdog.out', 'a'),
stderr=open('/tmp/pronote_watchdog.err', 'a') stderr=open('/tmp/pronote_watchdog.err', 'a')

View file

@ -227,7 +227,7 @@ def dnd_config_page():
pass pass
if config.is_enabled: if config.is_enabled:
script_path = '/app/watchdog_dnd.py' script_path = '/app/watchdog_dnd.py'
subprocess.Popen(['python3', script_path], subprocess.Popen(['python3', '/app/watchdog_gate.py', 'yeastar', script_path],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
flash("Configuration DND sauvegardée", "success") flash("Configuration DND sauvegardée", "success")
@ -330,7 +330,7 @@ def api_watchdog_restart():
# Relancer # Relancer
script_path = '/app/watchdog_dnd.py' script_path = '/app/watchdog_dnd.py'
subprocess.Popen(['python3', script_path], subprocess.Popen(['python3', '/app/watchdog_gate.py', 'yeastar', script_path],
stdout=subprocess.DEVNULL, stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL) stderr=subprocess.DEVNULL)

View file

@ -13,6 +13,7 @@ services:
- ./ent_watchdog.py:/app/ent_watchdog.py - ./ent_watchdog.py:/app/ent_watchdog.py
- ./pronote_watchdog.py:/app/pronote_watchdog.py - ./pronote_watchdog.py:/app/pronote_watchdog.py
- ./watchdog_dnd.py:/app/watchdog_dnd.py - ./watchdog_dnd.py:/app/watchdog_dnd.py
- ./watchdog_gate.py:/app/watchdog_gate.py:ro
- ./run_app_new.py:/app/run_app_new.py - ./run_app_new.py:/app/run_app_new.py
- ./wsgi.py:/app/wsgi.py - ./wsgi.py:/app/wsgi.py
- ./VERSION:/app/VERSION:ro - ./VERSION:/app/VERSION:ro

View file

@ -17,36 +17,36 @@ stderr_logfile=/dev/fd/2
stderr_logfile_maxbytes=0 stderr_logfile_maxbytes=0
[program:gmao_watchdog] [program:gmao_watchdog]
command=python3 /app/gmao_watchdog.py command=python3 /app/watchdog_gate.py outlook /app/gmao_watchdog.py
environment=DOCKER_GMAO_WORKDIR="/app",DOCKER_GMAO_ENVFILE="/app/.env.docker",PYTHONPATH="/app",HOME="/home/gmao" environment=DOCKER_GMAO_WORKDIR="/app",DOCKER_GMAO_ENVFILE="/app/.env.docker",PYTHONPATH="/app",HOME="/home/gmao"
directory=/app directory=/app
user=gmao user=gmao
autostart=true autostart=true
autorestart=true autorestart=false
stdout_logfile=/dev/fd/1 stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0 stdout_logfile_maxbytes=0
stderr_logfile=/dev/fd/2 stderr_logfile=/dev/fd/2
stderr_logfile_maxbytes=0 stderr_logfile_maxbytes=0
[program:ent_watchdog] [program:ent_watchdog]
command=python3 /app/ent_watchdog.py command=python3 /app/watchdog_gate.py ent /app/ent_watchdog.py
environment=DOCKER_GMAO_WORKDIR="/app",DOCKER_GMAO_ENVFILE="/app/.env.docker",PYTHONPATH="/app",HOME="/home/gmao" environment=DOCKER_GMAO_WORKDIR="/app",DOCKER_GMAO_ENVFILE="/app/.env.docker",PYTHONPATH="/app",HOME="/home/gmao"
directory=/app directory=/app
user=gmao user=gmao
autostart=true autostart=true
autorestart=true autorestart=false
stdout_logfile=/dev/fd/1 stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0 stdout_logfile_maxbytes=0
stderr_logfile=/dev/fd/2 stderr_logfile=/dev/fd/2
stderr_logfile_maxbytes=0 stderr_logfile_maxbytes=0
[program:pronote_watchdog] [program:pronote_watchdog]
command=python3 /app/pronote_watchdog.py command=python3 /app/watchdog_gate.py pronote /app/pronote_watchdog.py
environment=DOCKER_GMAO_WORKDIR="/app",DOCKER_GMAO_ENVFILE="/app/.env.docker",PYTHONPATH="/app",HOME="/home/gmao" environment=DOCKER_GMAO_WORKDIR="/app",DOCKER_GMAO_ENVFILE="/app/.env.docker",PYTHONPATH="/app",HOME="/home/gmao"
directory=/app directory=/app
user=gmao user=gmao
autostart=true autostart=true
autorestart=true autorestart=false
stdout_logfile=/dev/fd/1 stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0 stdout_logfile_maxbytes=0
stderr_logfile=/dev/fd/2 stderr_logfile=/dev/fd/2
@ -65,12 +65,12 @@ stderr_logfile=/dev/fd/2
stderr_logfile_maxbytes=0 stderr_logfile_maxbytes=0
[program:watchdog_dnd] [program:watchdog_dnd]
command=python3 /app/watchdog_dnd.py command=python3 /app/watchdog_gate.py yeastar /app/watchdog_dnd.py
environment=PYTHONPATH="/app",HOME="/home/gmao" environment=PYTHONPATH="/app",HOME="/home/gmao"
directory=/app directory=/app
user=gmao user=gmao
autostart=true autostart=true
autorestart=true autorestart=false
stdout_logfile=/dev/fd/1 stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0 stdout_logfile_maxbytes=0
stderr_logfile=/dev/fd/2 stderr_logfile=/dev/fd/2

91
watchdog_gate.py Normal file
View file

@ -0,0 +1,91 @@
#!/usr/bin/env python3
"""Start an integration watchdog only when its configuration is usable."""
import os
import sys
def integration_is_configured(name):
import pymysql
from sqlalchemy.engine import make_url
database_url = make_url(os.environ['DATABASE_URL'])
connection = pymysql.connect(
host=database_url.host,
port=database_url.port or 3306,
user=database_url.username,
password=database_url.password,
database=database_url.database,
connect_timeout=5,
)
checks = {
"outlook": """
SELECT EXISTS(SELECT 1 FROM gmao_context WHERE auto_interpret_enabled = 1)
AND EXISTS(SELECT 1 FROM outlook_accounts WHERE is_active = 1)
""",
"ent": """
SELECT EXISTS(SELECT 1 FROM gmao_context WHERE auto_interpret_enabled = 1)
AND EXISTS(SELECT 1 FROM ent_credentials WHERE is_active = 1)
""",
"pronote": """
SELECT EXISTS(SELECT 1 FROM gmao_context WHERE pronote_sync_enabled = 1)
AND EXISTS(
SELECT 1 FROM pronote_session
WHERE session_token IS NOT NULL AND session_token != ''
)
""",
"yeastar": """
SELECT username_encrypted, password_encrypted
FROM yeastar_config
WHERE is_active = 1 AND base_url != ''
AND EXISTS(SELECT 1 FROM yeastar_dnd_config WHERE is_enabled = 1)
LIMIT 1
""",
}
if name not in checks:
raise ValueError(f"Integration inconnue: {name}")
try:
with connection.cursor() as cursor:
cursor.execute(checks[name])
result = cursor.fetchone()
if name != "yeastar":
return bool(result and result[0])
if not result:
return False
from cryptography.fernet import Fernet
key = os.environ.get('YEASTAR_ENCRYPTION_KEY') or os.environ.get(
'OUTLOOK_ENCRYPTION_KEY'
)
if not key:
return False
try:
cipher = Fernet(key.encode())
return bool(cipher.decrypt(result[0]) and cipher.decrypt(result[1]))
except Exception:
return False
finally:
connection.close()
def main():
if len(sys.argv) != 3:
raise SystemExit("usage: watchdog_gate.py INTEGRATION SCRIPT")
integration, script = sys.argv[1:]
try:
configured = integration_is_configured(integration)
except Exception as exc:
print(f"[{integration}] watchdog non lance: configuration illisible ({exc})")
return 0
if not configured:
print(f"[{integration}] watchdog non lance: integration non configuree")
return 0
print(f"[{integration}] configuration valide, lancement de {script}")
os.execv(sys.executable, [sys.executable, script])
if __name__ == "__main__":
raise SystemExit(main())