agnes-the-ai-analyst/services/telegram_bot/runner.py
Petr f2d3d156e3 Move standalone services from server/ to services/
Extract 4 self-contained services into services/ module:
- server/telegram_bot/ -> services/telegram_bot/
- server/ws_gateway/ -> services/ws_gateway/
- server/corporate_memory/ -> services/corporate_memory/
- server/session_collector.py -> services/session_collector/

Each service now has its own systemd/ directory with .service and .timer files.
deploy.sh updated to auto-discover service units from services/*/systemd/*.

server/ now contains only deployment infrastructure (deploy.sh, setup scripts,
bin/ management tools, sudoers, nginx config).

All imports updated: webapp/app.py, server/bin/ scripts, systemd ExecStart paths.
2026-03-09 12:54:30 +01:00

73 lines
2.4 KiB
Python

"""
Script runner for Telegram bot - executes user notification scripts on demand.
Used by callback query handler when user clicks "Run" button in /status.
Runs the script as the owning user via the notify-scripts helper.
"""
import json
import logging
import subprocess
from . import config
logger = logging.getLogger(__name__)
NOTIFY_SCRIPTS_BIN = "/usr/local/bin/notify-scripts"
def run_user_script(username: str, script_name: str) -> dict | None:
"""Run a notification script as the specified user and return parsed JSON output.
Returns None on error, or the parsed JSON dict on success.
"""
if not script_name.endswith(".py"):
logger.warning(f"Not a Python script: {script_name}")
return None
try:
result = subprocess.run(
["/usr/bin/sudo", "-u", username, NOTIFY_SCRIPTS_BIN, "run", script_name],
capture_output=True,
text=True,
timeout=config.SCRIPT_TIMEOUT_SECONDS + 10, # extra margin over inner timeout
)
if result.returncode != 0:
# notify-scripts prints JSON error to stdout on failure
try:
error_info = json.loads(result.stdout)
logger.warning(
f"Script {script_name} (user={username}) failed: "
f"{error_info.get('error', 'unknown')}"
)
except (json.JSONDecodeError, Exception):
logger.warning(
f"Script {script_name} (user={username}) exited with code "
f"{result.returncode}: {result.stderr[:500]}"
)
return None
stdout = result.stdout.strip()
if not stdout:
logger.warning(f"Script {script_name} produced no stdout")
return None
parsed = json.loads(stdout)
logger.info(
f"Script {script_name} output: "
f"image_path={parsed.get('image_path', 'MISSING')}"
)
return parsed
except subprocess.TimeoutExpired:
logger.error(
f"Script {script_name} timed out after {config.SCRIPT_TIMEOUT_SECONDS}s"
)
return None
except json.JSONDecodeError as e:
logger.error(f"Script {script_name} returned invalid JSON: {e}")
return None
except Exception:
logger.exception(f"Error running script {script_name} for user {username}")
return None